/* memory-tls.c
 *
 * Copyright (C) 2006-2020 wolfSSL Inc.
 *
 * This file is part of wolfSSL. (formerly known as CyaSSL)
 *
 * 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 2 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-1301, USA
 */


/* in memory TLS connection with I/O callbacks, no sockets
 *
 gcc -Wall memory-tls.c  -l wolfssl -lpthread

*/

#include <wolfssl/options.h>
#include <wolfssl/ssl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

/* client messages to server in memory */
unsigned char to_server[1024*17];
int server_bytes;
int server_write_idx;
int server_read_idx;
pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  server_cond  = PTHREAD_COND_INITIALIZER;

/* server messages to client in memory */
unsigned char to_client[1024*17];
int client_bytes;
int client_write_idx;
int client_read_idx;
pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  client_cond  = PTHREAD_COND_INITIALIZER;


/* server send callback */
int ServerSend(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
    pthread_mutex_lock(&client_mutex);

    memcpy(&to_client[client_write_idx], buf, sz);
    client_write_idx += sz;
    client_bytes += sz;

    pthread_cond_signal(&client_cond);
    pthread_mutex_unlock(&client_mutex);

    return sz;
}


/* server recv callback */
int ServerRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
    pthread_mutex_lock(&server_mutex);

    while (server_bytes - server_read_idx < sz)
        pthread_cond_wait(&server_cond, &server_mutex);

    memcpy(buf, &to_server[server_read_idx], sz);
    server_read_idx += sz;

    if (server_read_idx == server_write_idx) {
        server_read_idx = server_write_idx = 0;
        server_bytes = 0;
    }

    pthread_mutex_unlock(&server_mutex);

    return sz;
}


/* client send callback */
int ClientSend(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
    pthread_mutex_lock(&server_mutex);

    memcpy(&to_server[server_write_idx], buf, sz);
    server_write_idx += sz;
    server_bytes += sz;

    pthread_cond_signal(&server_cond);
    pthread_mutex_unlock(&server_mutex);

    return sz;
}


/* client recv callback */
int ClientRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
    pthread_mutex_lock(&client_mutex);

    while (client_bytes - client_read_idx < sz)
        pthread_cond_wait(&client_cond, &client_mutex);

    memcpy(buf, &to_client[client_read_idx], sz);
    client_read_idx += sz;

    if (client_read_idx == client_write_idx) {
        client_read_idx = client_write_idx = 0;
        client_bytes = 0;
    }

    pthread_mutex_unlock(&client_mutex);

    return sz;
}


static void err_sys(const char* msg)
{
    printf("wolfSSL error: %s\n", msg);
    exit(1);
}


#define key "../certs/server-key.pem"
#define cert "../certs/server-cert.pem"
#define cacert "../certs/ca-cert.pem"


static void* client_thread(void* args)
{
    /* set up client */
    WOLFSSL_CTX* cli_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
    if (cli_ctx == NULL) err_sys("bad client ctx new");

    int ret = wolfSSL_CTX_load_verify_locations(cli_ctx, cacert, NULL);
    if (ret != WOLFSSL_SUCCESS) err_sys("bad ca load");

    wolfSSL_SetIOSend(cli_ctx, ClientSend);
    wolfSSL_SetIORecv(cli_ctx, ClientRecv);

    WOLFSSL* cli_ssl = wolfSSL_new(cli_ctx);
    if (cli_ctx == NULL) err_sys("bad client new");

    ret = wolfSSL_connect(cli_ssl);
    if (ret != WOLFSSL_SUCCESS) err_sys("bad client tls connect");
    printf("wolfSSL client success!\n");

    ret = wolfSSL_write(cli_ssl, "hello memory wolfSSL!", 21);

    /* clean up */
    wolfSSL_free(cli_ssl);
    wolfSSL_CTX_free(cli_ctx);

    return NULL;
}


int main()
{
    /* set up server */
    WOLFSSL_CTX* srv_ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method());
    if (srv_ctx == NULL) err_sys("bad server ctx new");

    int ret = wolfSSL_CTX_use_PrivateKey_file(srv_ctx, key, WOLFSSL_FILETYPE_PEM);
    if (ret != WOLFSSL_SUCCESS) err_sys("bad server key file load");

    ret = wolfSSL_CTX_use_certificate_file(srv_ctx, cert, WOLFSSL_FILETYPE_PEM);
    if (ret != WOLFSSL_SUCCESS) err_sys("bad server cert file load");

    wolfSSL_SetIOSend(srv_ctx, ServerSend);
    wolfSSL_SetIORecv(srv_ctx, ServerRecv);

    WOLFSSL* srv_ssl = wolfSSL_new(srv_ctx);
    if (srv_ctx == NULL) err_sys("bad server new");

    /* start client thread */
    pthread_t tid;
    pthread_create(&tid, 0, client_thread, NULL);

    /* accept tls connection without tcp sockets */
    ret = wolfSSL_accept(srv_ssl);
    if (ret != WOLFSSL_SUCCESS) err_sys("bad server tls accept");
    printf("wolfSSL accept success!\n");

    /* read msg post handshake from client */
    unsigned char buf[80];
    memset(buf, 0, sizeof(buf));
    ret = wolfSSL_read(srv_ssl, buf, sizeof(buf)-1);
    printf("client msg = %s\n", buf);

    /* clean up */
    wolfSSL_free(srv_ssl);
    wolfSSL_CTX_free(srv_ctx);

    return 0;
}
