/* async_server.c
 *
 * Copyright (C) 2006-2026 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

/* TLS server demonstrating asynchronous cryptography features and non-blocking
 * operation using WOLFSSL_USER_IO callbacks. */

#ifdef HAVE_CONFIG_H
    #include <config.h>
#endif

/* std */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* socket */
#ifndef NET_USER_HEADER
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#define HAVE_SIGNAL
#ifdef HAVE_SIGNAL
#include <signal.h> /* for catching ctrl+c */
#endif

/* wolfSSL */
#ifdef WOLFSSL_USER_SETTINGS
    #include "user_settings.h"
#else
    #include <wolfssl/options.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/ssl.h>
#include <wolfssl/wolfio.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#ifdef WOLF_CRYPTO_CB
#include <wolfssl/wolfcrypt/cryptocb.h>
#endif
#include <wolfssl/certs_test.h>
#include "examples/async/async_tls.h"

#if ASYNC_ECC_ONLY
    #ifndef HAVE_ECC
        #error ASYNC_ECC_ONLY requires HAVE_ECC
    #endif
#else
    #ifndef NO_RSA
        #error RSA not supported in this example configuration
    #endif
#endif

static int mSockfd = SOCKET_INVALID;
static int mConnd = SOCKET_INVALID;
static int mShutdown = 0;

#ifdef HAVE_SIGNAL
static void sig_handler(const int sig)
{
#ifdef DEBUG_WOLFSSL
    fprintf(stderr, "SIGINT handled = %d.\n", sig);
#else
    (void)sig;
#endif

    mShutdown = 1;
    if (mConnd != SOCKET_INVALID) {
        NET_CLOSE(mConnd);
        mConnd = SOCKET_INVALID;
    }
    if (mSockfd != SOCKET_INVALID) {
        NET_CLOSE(mSockfd);
        mSockfd = SOCKET_INVALID;
    }
}
#endif

/* ------------------------------------------------------------------ */
/* POSIX transport helpers (replace with your BSP/port layer).         */
/* ------------------------------------------------------------------ */
#ifndef NET_USER_HEADER
static int posix_set_nonblocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        return -1;
    }
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
#endif

/* ------------------------------------------------------------------ */
/* WOLFSSL_USER_IO callbacks.                                          */
/* ------------------------------------------------------------------ */
static void usage(const char* prog)
{
    printf("usage: %s [--ecc|--x25519] [--mutual] [--tls12] [port]\n", prog);
}

static const char* group_name(word16 group)
{
    switch (group) {
        case WOLFSSL_ECC_SECP256R1:
            return "secp256r1";
        case WOLFSSL_ECC_X25519:
            return "x25519";
        default:
            return "unknown";
    }
}

static int parse_server_args(int argc, char** argv, int* port, word16* group,
    int* mutual, int* tls12)
{
    int i;
    int port_set = 0;

    *port = DEFAULT_PORT;
    *group = WOLFSSL_ECC_SECP256R1;
    *mutual = 0;
    *tls12 = 0;

    for (i = 1; i < argc; i++) {
        if (XSTRCMP(argv[i], "--ecc") == 0) {
            *group = WOLFSSL_ECC_SECP256R1;
        }
        else if (XSTRCMP(argv[i], "--x25519") == 0) {
            *group = WOLFSSL_ECC_X25519;
        }
        else if (XSTRCMP(argv[i], "--mutual") == 0) {
            *mutual = 1;
        }
        else if (XSTRCMP(argv[i], "--tls12") == 0) {
            *tls12 = 1;
        }
        else if (XSTRCMP(argv[i], "--help") == 0) {
            return -1;
        }
        else if (!port_set) {
            *port = atoi(argv[i]);
            port_set = 1;
        }
        else {
            return -1;
        }
    }

    return 0;
}

int server_async_test(int argc, char** argv)
{
    int ret = -1;
    struct sockaddr_in servAddr;
    struct sockaddr_in clientAddr;
    socklen_t          size = sizeof(clientAddr);
    char               buff[TEST_BUF_SZ];
    size_t             len;
    const char*        reply = "I hear ya fa shizzle!\n";
    int                on;
    int                port = DEFAULT_PORT;
    word16             group = WOLFSSL_ECC_SECP256R1;
    int                err = 0;
    const char*        mode = NULL;
    int                mutual = 0;
    int                tls12 = 0;
#ifdef WOLFSSL_ASYNC_CRYPT
    int devId = INVALID_DEVID;
#endif
#ifdef WOLFSSL_DEBUG_NONBLOCK
    int wouldblock_count = 0;
    int pending_count = 0;
#endif
#ifdef WOLF_CRYPTO_CB
    AsyncTlsCryptoCbCtx cryptoCbCtx;
#endif
#ifdef WOLFSSL_STATIC_MEMORY
    static byte memory[300000];
    static byte memoryIO[34500];
    #if !defined(WOLFSSL_STATIC_MEMORY_LEAN)
    WOLFSSL_MEM_CONN_STATS ssl_stats;
    #endif
#endif

    /* declare wolfSSL objects */
    WOLFSSL_CTX* ctx = NULL;
    WOLFSSL*     ssl = NULL;

#ifdef HAVE_SIGNAL
    if ((signal(SIGINT, sig_handler)) == SIG_ERR) {
        fprintf(stderr, "ERROR: failed to listen to SIGINT (errno: %d)\n",errno);
        goto exit;
    }
#endif

    if (parse_server_args(argc, argv, &port, &group, &mutual, &tls12) != 0) {
        usage(argv[0]);
        return 0;
    }
    mode = group_name(group);
    printf("Async server mode: %s, TLS %s%s\n", mode,
        tls12 ? "1.2" : "1.3", mutual ? ", mutual auth" : "");

    /* Initialize the server address struct with zeros */
    memset(&servAddr, 0, sizeof(servAddr));

    /* Fill in the server address */
    servAddr.sin_family      = AF_INET;         /* using IPv4      */
    servAddr.sin_port        = htons(port);
    servAddr.sin_addr.s_addr = INADDR_ANY;      /* from anywhere   */

    /* Create a socket that uses an internet IPv4 address,
     * Sets the socket to be stream based (TCP),
     * 0 means choose the default protocol. */
    if ((mSockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        fprintf(stderr, "ERROR: failed to create the socket\n");
        goto exit;
    }

    /* make sure server is setup for reuse addr/port */
    on = 1;
    if (setsockopt(mSockfd, SOL_SOCKET, SO_REUSEADDR,
            (char*)&on, (socklen_t)sizeof(on)) != 0) {
        fprintf(stderr, "ERROR: failed to set SO_REUSEADDR (errno: %d)\n",errno);
        goto exit;
    }
#ifdef SO_REUSEPORT
    if (setsockopt(mSockfd, SOL_SOCKET, SO_REUSEPORT,
               (char*)&on, (socklen_t)sizeof(on)) != 0) {
        fprintf(stderr, "ERROR: failed to set SO_REUSEPORT (errno: %d)\n",errno);
        goto exit;
    }
#endif

    /* Bind the server socket to our port */
    if (bind(mSockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) {
        fprintf(stderr, "ERROR: failed to bind\n");
        goto exit;
    }

    /* Listen for a new connection, allow 5 pending connections */
    if (listen(mSockfd, 5) == -1) {
        fprintf(stderr, "ERROR: failed to listen\n");
        goto exit;
    }
    {
        const char* ready = getenv(WOLFSSL_ASYNC_READYFILE_ENV);
        if (ready != NULL) {
            (void)async_readyfile_touch(ready);
        }
    }

    /*---------------------------------*/
    /* Start of wolfSSL initialization and configuration */
    /*---------------------------------*/
#ifdef DEBUG_WOLFSSL
    wolfSSL_Debugging_ON();
#endif

    /* Initialize wolfSSL */
    if (wolfSSL_Init() != WOLFSSL_SUCCESS) {
        fprintf(stderr, "ERROR: Failed to initialize the library\n");
        goto exit;
    }
#ifdef WOLFSSL_ASYNC_CRYPT
    if (wolfAsync_DevOpenThread(&devId, NULL) != 0) {
        goto exit;
    }
#endif
#ifdef WOLF_CRYPTO_CB
    /* Crypto callbacks require a valid devId. When no hardware async driver
     * sets one (e.g. Cavium/Intel QA/SW), assign one explicitly. */
    if (devId == INVALID_DEVID)
        devId = 1;
    XMEMSET(&cryptoCbCtx, 0, sizeof(cryptoCbCtx));
    if (wc_CryptoCb_RegisterDevice(devId, AsyncTlsCryptoCb, &cryptoCbCtx) != 0) {
        fprintf(stderr, "ERROR: wc_CryptoCb_RegisterDevice failed\n");
        goto exit;
    }
#endif

    /* Create and initialize WOLFSSL_CTX */
#ifdef WOLFSSL_STATIC_MEMORY
    {
        wolfSSL_method_func method;
    #ifndef WOLFSSL_NO_TLS12
        if (tls12)
            method = wolfTLSv1_2_server_method_ex;
        else
    #endif
            method = wolfSSLv23_server_method_ex;
        if (wolfSSL_CTX_load_static_memory(&ctx, method, memory,
                sizeof(memory), 0, 1) != WOLFSSL_SUCCESS) {
            fprintf(stderr, "ERROR: unable to load static memory\n");
            goto exit;
        }
        if (wolfSSL_CTX_load_static_memory(&ctx, NULL, memoryIO,
                sizeof(memoryIO),
                WOLFMEM_IO_POOL_FIXED | WOLFMEM_TRACK_STATS, 1)
                != WOLFSSL_SUCCESS) {
            fprintf(stderr, "ERROR: unable to load static IO memory\n");
            goto exit;
        }
    }
#else
    #ifndef WOLFSSL_NO_TLS12
    if (tls12)
        ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method());
    else
    #endif
        ctx = wolfSSL_CTX_new(wolfSSLv23_server_method());
#endif /* WOLFSSL_STATIC_MEMORY */
    if (ctx == NULL) {
        fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n");
        ret = -1;
        goto exit;
    }
#ifdef WOLFSSL_ASYNC_CRYPT
    wolfSSL_CTX_SetDevId(ctx, devId);
#endif

    wolfSSL_SetIORecv(ctx, NET_IO_RECV_CB);
    wolfSSL_SetIOSend(ctx, NET_IO_SEND_CB);

    if (group == WOLFSSL_ECC_X25519) {
    #ifdef HAVE_ED25519
        ret = wolfSSL_CTX_use_certificate_buffer(ctx, server_ed25519_cert,
            sizeof_server_ed25519_cert, WOLFSSL_FILETYPE_ASN1);
        if (ret != WOLFSSL_SUCCESS) {
            fprintf(stderr,
                "ERROR: failed to load ED25519 server cert buffer.\n");
            goto exit;
        }

        ret = wolfSSL_CTX_use_PrivateKey_buffer(ctx, server_ed25519_key,
            sizeof_server_ed25519_key, WOLFSSL_FILETYPE_ASN1);
        if (ret != WOLFSSL_SUCCESS) {
            fprintf(stderr,
                "ERROR: failed to load ED25519 server key buffer.\n");
            goto exit;
        }

        if (mutual) {
            /* client-ed25519 is self-signed, so load it as its own CA */
            ret = wolfSSL_CTX_load_verify_buffer(ctx, client_ed25519_cert,
                sizeof_client_ed25519_cert, WOLFSSL_FILETYPE_ASN1);
            if (ret != WOLFSSL_SUCCESS) {
                fprintf(stderr,
                    "ERROR: failed to load ED25519 client CA cert.\n");
                goto exit;
            }
        }
    #else
        fprintf(stderr, "ERROR: --x25519 requires HAVE_ED25519 for certs\n");
        goto exit;
    #endif
    }
    else {
        ret = wolfSSL_CTX_use_certificate_buffer(ctx, serv_ecc_der_256,
            sizeof_serv_ecc_der_256, WOLFSSL_FILETYPE_ASN1);
        if (ret != WOLFSSL_SUCCESS) {
            fprintf(stderr, "ERROR: failed to load ECC server cert buffer.\n");
            goto exit;
        }

        ret = wolfSSL_CTX_use_PrivateKey_buffer(ctx, ecc_key_der_256,
            sizeof_ecc_key_der_256, WOLFSSL_FILETYPE_ASN1);
        if (ret != WOLFSSL_SUCCESS) {
            fprintf(stderr, "ERROR: failed to load ECC server key buffer.\n");
            goto exit;
        }

        if (mutual) {
            /* client-ecc-cert is self-signed, so load it as its own CA */
            ret = wolfSSL_CTX_load_verify_buffer(ctx, cliecc_cert_der_256,
                sizeof_cliecc_cert_der_256, WOLFSSL_FILETYPE_ASN1);
            if (ret != WOLFSSL_SUCCESS) {
                fprintf(stderr,
                    "ERROR: failed to load ECC client CA cert.\n");
                goto exit;
            }
        }
    }

    if (mutual) {
        wolfSSL_CTX_set_verify(ctx,
            WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
    }
    else {
        wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL);
    }

    /* Continue to accept clients until mShutdown is issued */
    while (!mShutdown) {
        printf("Waiting for a connection...\n");

        /* Accept client connections */
        if ((mConnd = NET_ACCEPT(mSockfd,
            (struct sockaddr*)&clientAddr, &size))
            == -1) {
            fprintf(stderr, "ERROR: failed to accept the connection\n\n");
            ret = -1; goto exit;
        }
        if (NET_SET_NONBLOCKING(mConnd) != 0) {
            fprintf(stderr, "ERROR: failed to set non-blocking socket\n");
            ret = -1; goto exit;
        }

        /* Create a WOLFSSL object */
        if ((ssl = wolfSSL_new(ctx)) == NULL) {
            fprintf(stderr, "ERROR: failed to create WOLFSSL object\n");
            ret = -1; goto exit;
        }

        wolfSSL_SetIOReadCtx(ssl, (void*)(intptr_t)mConnd);
        wolfSSL_SetIOWriteCtx(ssl, (void*)(intptr_t)mConnd);

        /* UseKeyShare is TLS 1.3 only */
        if (!tls12) {
            for (;;) {
                ret = wolfSSL_UseKeyShare(ssl, group);
                if (ret == WOLFSSL_SUCCESS) {
                    break;
                }
                if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    pending_count++;
#endif
#ifdef WOLFSSL_ASYNC_CRYPT
                    if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) {
                        goto exit;
                    }
#endif
                    continue;
                }
                goto exit;
            }
        }

        /* Establish TLS connection */
        for (;;) {
            ret = wolfSSL_accept(ssl);
            if (ret == WOLFSSL_SUCCESS) {
                break;
            }
            err = wolfSSL_get_error(ssl, 0);
            if (err == WC_NO_ERR_TRACE(WC_PENDING_E) ||
                err == WOLFSSL_ERROR_WANT_READ ||
                err == WOLFSSL_ERROR_WANT_WRITE) {
                if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    pending_count++;
#endif
#ifdef WOLFSSL_ASYNC_CRYPT
                    if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) {
                        goto exit;
                    }
#endif
                }
                else {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    wouldblock_count++;
#endif
                }
                continue;
            }
            fprintf(stderr, "ERROR: wolfSSL_accept failed: %d (%s)\n",
                err, wolfSSL_ERR_reason_error_string(err));
            goto exit;
        }

        {
            const char* cipher = wolfSSL_get_cipher_name(ssl);
            const char* curve = wolfSSL_get_curve_name(ssl);
            printf("Negotiated cipher: %s\n",
                cipher != NULL ? cipher : "unknown");
            printf("Negotiated group: %s\n",
                curve != NULL ? curve : "unknown");
        }
        printf("Client connected successfully\n");

        /* Read the client data into our buff array */
        memset(buff, 0, sizeof(buff));
        for (;;) {
            ret = wolfSSL_read(ssl, buff, sizeof(buff) - 1);
            if (ret > 0) {
                break;
            }
            err = wolfSSL_get_error(ssl, 0);
            if (err == WC_NO_ERR_TRACE(WC_PENDING_E) ||
                err == WOLFSSL_ERROR_WANT_READ ||
                err == WOLFSSL_ERROR_WANT_WRITE) {
                if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    pending_count++;
#endif
#ifdef WOLFSSL_ASYNC_CRYPT
                    if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) {
                        goto exit;
                    }
#endif
                }
                else {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    wouldblock_count++;
#endif
                }
                continue;
            }
            fprintf(stderr, "ERROR: wolfSSL_read failed: %d (%s)\n",
                err, wolfSSL_ERR_reason_error_string(err));
            goto exit;
        }

        /* Print to stdout any data the client sends */
        printf("Client: %s\n", buff);

        /* Check for server shutdown command */
        if (strncmp(buff, "shutdown", 8) == 0) {
            printf("Shutdown command issued!\n");
            mShutdown = 1;
        }

        /* Write our reply into buff */
        memset(buff, 0, sizeof(buff));
        memcpy(buff, reply, strlen(reply));
        len = strnlen(buff, sizeof(buff));

        /* Reply back to the client */
        for (;;) {
            ret = wolfSSL_write(ssl, buff, (int)len);
            if (ret > 0) {
                break;
            }
            err = wolfSSL_get_error(ssl, 0);
            if (err == WC_NO_ERR_TRACE(WC_PENDING_E) ||
                err == WOLFSSL_ERROR_WANT_READ ||
                err == WOLFSSL_ERROR_WANT_WRITE) {
                if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    pending_count++;
#endif
#ifdef WOLFSSL_ASYNC_CRYPT
                    if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) {
                        goto exit;
                    }
#endif
                }
                else {
#ifdef WOLFSSL_DEBUG_NONBLOCK
                    wouldblock_count++;
#endif
                }
                continue;
            }
            goto exit;
        }

#ifdef WOLFSSL_DEBUG_NONBLOCK
        printf("WANT_READ/WRITE count: %d\n", wouldblock_count);
        printf("WC_PENDING_E count: %d\n", pending_count);
#endif

        /* Cleanup after this connection */
#if defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN)
        if (ssl != NULL &&
                wolfSSL_is_static_memory(ssl, &ssl_stats) == 1) {
            fprintf(stderr, "peak connection memory = %d\n",
                ssl_stats.peakMem);
            fprintf(stderr, "current memory in use  = %d\n",
                ssl_stats.curMem);
            fprintf(stderr, "peak connection allocs = %d\n",
                ssl_stats.peakAlloc);
            fprintf(stderr, "total connection allocs   = %d\n",
                ssl_stats.totalAlloc);
            fprintf(stderr, "total connection frees    = %d\n",
                ssl_stats.totalFr);
        }
#endif
        wolfSSL_shutdown(ssl);
        if (ssl) {
            wolfSSL_free(ssl);
            ssl = NULL;
        }
        if (mConnd != SOCKET_INVALID) {
            NET_CLOSE(mConnd);
            mConnd = SOCKET_INVALID;
        }
    }

    printf("Shutdown complete\n");
#ifdef WOLFSSL_DEBUG_NONBLOCK
    printf("WANT_READ/WRITE count: %d\n", wouldblock_count);
    printf("WC_PENDING_E count: %d\n", pending_count);
#endif
    ret = 0;

exit:
    /* Cleanup and return */
    if (ssl)
        wolfSSL_free(ssl);
    if (mConnd != SOCKET_INVALID) {
        NET_CLOSE(mConnd);
        mConnd = SOCKET_INVALID;
    }
    if (mSockfd != SOCKET_INVALID) {
        NET_CLOSE(mSockfd);
        mSockfd = SOCKET_INVALID;
    }
    if (ctx)
        wolfSSL_CTX_free(ctx);
#ifdef WOLF_CRYPTO_CB
    wc_CryptoCb_UnRegisterDevice(devId);
#endif
#ifdef WOLFSSL_ASYNC_CRYPT
    if (devId != INVALID_DEVID) {
        wolfAsync_DevClose(&devId);
    }
#endif
    {
        const char* ready = getenv(WOLFSSL_ASYNC_READYFILE_ENV);
        if (ready != NULL) {
            async_readyfile_clear(ready);
        }
    }
    wolfSSL_Cleanup();

    return ret;
}

#ifndef NO_MAIN_DRIVER
int main(int argc, char** argv)
{
    return server_async_test(argc, argv);
}
#endif /* !NO_MAIN_DRIVER */
