/* test.h
 *
 * Copyright (C) 2014-2019 wolfSSL Inc.
 *
 * This file is part of wolfSSH.
 *
 * wolfSSH 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.
 *
 * wolfSSH 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 wolfSSH.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#ifndef _WOLFSSH_TEST_H_
#define _WOLFSSH_TEST_H_

#include <wolfssh/ssh.h>

#include <tinystdio.h>
/*#include <stdlib.h>*/
#include <ctype.h>
/*#include <wolfssh/error.h>*/

/* Socket Handling */
#ifndef WOLFSSH_SOCKET_INVALID
#if defined(USE_WINDOWS_API) || defined(MICROCHIP_MPLAB_HARMONY)
    #define WOLFSSH_SOCKET_INVALID  ((SOCKET_T)INVALID_SOCKET)
#elif defined(WOLFSSH_TIRTOS)
    #define WOLFSSH_SOCKET_INVALID  ((SOCKET_T)-1)
#else
    #define WOLFSSH_SOCKET_INVALID  (SOCKET_T)(0)
#endif
#endif /* WOLFSSH_SOCKET_INVALID */

#ifndef WOLFSSL_SOCKET_IS_INVALID
#if defined(USE_WINDOWS_API) || defined(WOLFSSL_TIRTOS)
    #define WOLFSSL_SOCKET_IS_INVALID(s)  ((SOCKET_T)(s) == WOLFSSL_SOCKET_INVALID)
#else
    #define WOLFSSL_SOCKET_IS_INVALID(s)  ((SOCKET_T)(s) < WOLFSSL_SOCKET_INVALID)
#endif
#endif /* WOLFSSL_SOCKET_IS_INVALID */


#if defined(__MACH__) || defined(USE_WINDOWS_API)
    #ifndef _SOCKLEN_T
        typedef int socklen_t;
    #endif
#endif


#ifdef USE_WINDOWS_API
    #define WCLOSESOCKET(s) closesocket(s)
    #define WSTARTTCP() do { WSADATA wsd; WSAStartup(0x0002, &wsd); } while(0)
#elif defined(MICROCHIP_TCPIP) || defined(MICROCHIP_MPLAB_HARMONY)
    #ifdef MICROCHIP_MPLAB_HARMONY
        #define WCLOSESOCKET(s) TCPIP_TCP_Close((s))
    #else
        #define WCLOSESOCKET(s) closesocket((s))
    #endif
    #define WSTARTTCP()
#elif defined(WOLFSSL_NUCLEUS)
    #define WCLOSESOCKET(s) NU_Close_Socket((s))
    #define WSTARTTCP()
#else
    #define WCLOSESOCKET(s) close(s)
    #define WSTARTTCP()
#endif


#ifdef SINGLE_THREADED
    typedef unsigned int  THREAD_RETURN;
    typedef void*         THREAD_TYPE;
    #define WOLFSSH_THREAD
#else
    #if defined(_POSIX_THREADS) && !defined(__MINGW32__)
        typedef void*         THREAD_RETURN;
        typedef pthread_t     THREAD_TYPE;
        #define WOLFSSH_THREAD
        #define INFINITE -1
        #define WAIT_OBJECT_0 0L
    #elif defined(WOLFSSL_NUCLEUS)
        typedef unsigned int  THREAD_RETURN;
        typedef intptr_t      THREAD_TYPE;
        #define WOLFSSH_THREAD
    #else
        typedef unsigned int  THREAD_RETURN;
        typedef intptr_t      THREAD_TYPE;
        #define WOLFSSH_THREAD __stdcall
    #endif
#endif

#ifdef TEST_IPV6
    typedef struct sockaddr_in6 SOCKADDR_IN_T;
    #define AF_INET_V AF_INET6
#else
    #ifndef WOLFSSL_NUCLEUS
        typedef struct sockaddr_in SOCKADDR_IN_T;
    #endif
    #define AF_INET_V AF_INET
#endif


#define serverKeyRsaPemFile "./keys/server-key-rsa.pem"


#ifndef TEST_IPV6
    static const char* const wolfSshIp = "127.0.0.1";
#else /* TEST_IPV6 */
    static const char* const wolfSshIp = "::1";
#endif /* TEST_IPV6 */

#ifdef __GNUC__
    #define WS_NORETURN __attribute__((noreturn))
#else
    #define WS_NORETURN
#endif

#ifdef USE_WINDOWS_API
    #pragma warning(push)
    #pragma warning(disable:4996)
    /* For Windows builds, disable compiler warnings for:
    * - 4996: deprecated function */
#endif

#if defined(WOLFSSH_TEST_CLIENT) || defined(WOLFSSH_TEST_SERVER)

#ifdef USE_WINDOWS_API
    #pragma warning(pop)
#endif


#endif /* WOLFSSH_TEST_CLIENT || WOLFSSH_TEST_SERVER */


#ifndef XNTOHS
    #define XNTOHS(a) ntohs((a))
#endif

#if (defined(WOLFSSH_TEST_SERVER) || defined(WOLFSSH_TEST_CLIENT))

#ifdef WOLFSSL_NUCLEUS
	#define WFD_SET_TYPE FD_SET
	#define WFD_SET NU_FD_Set
	#define WFD_ZERO NU_FD_Init
	#define WFD_ISSET NU_FD_Check
#else
	#define WFD_SET_TYPE fd_set
	#define WFD_SET FD_SET
	#define WFD_ZERO FD_ZERO
	#define WFD_ISSET FD_ISSET
#endif

/* returns 1 or greater when something is ready to be read */
static INLINE int wSelect(int nfds, WFD_SET_TYPE* recvfds,
        WFD_SET_TYPE *writefds, WFD_SET_TYPE *errfds,  struct timeval* timeout)
{
#ifdef WOLFSSL_NUCLEUS
    int ret = NU_Select (nfds, recvfds,  writefds, errfds,
            (UNSIGNED)timeout->tv_sec);
    if (ret == NU_SUCCESS) {
        return 1;
    }
    return 0;
#else
    return select(nfds, recvfds, writefds, errfds, timeout);
#endif
}

#endif /* WOLFSSH_TEST_SERVER || WOLFSSH_TEST_CLIENT */


/* Wolf Root Directory Helper */
/* KEIL-RL File System does not support relative directory */
#if !defined(WOLFSSL_MDK_ARM) && !defined(WOLFSSL_KEIL_FS) && !defined(WOLFSSL_TIRTOS) \
    && !defined(NO_WOLFSSL_DIR) && !defined(WOLFSSL_NUCLEUS)
    /* Maximum depth to search for WolfSSL root */
    #define MAX_WOLF_ROOT_DEPTH 5

    static INLINE int ChangeToWolfSshRoot(void)
    {
        #if !defined(NO_FILESYSTEM)
            int depth, res;
            WFILE* file;
            for(depth = 0; depth <= MAX_WOLF_ROOT_DEPTH; depth++) {
                if (WFOPEN(&file, serverKeyRsaPemFile, "rb") == 0) {
                    WFCLOSE(file);
                    return depth;
                }
            #ifdef USE_WINDOWS_API
                res = SetCurrentDirectoryA("..\\");
            #else
                res = chdir("../");
            #endif
                if (res < 0) {
                    printf("chdir to ../ failed!\n");
                    break;
                }
            }

            err_sys("wolfSSH root not found");
            return -1;
        #else
            return 0;
        #endif
    }
#endif /* !defined(WOLFSSL_MDK_ARM) && !defined(WOLFSSL_KEIL_FS) && !defined(WOL
FSSL_TIRTOS) */




#ifdef WOLFSSH_SFTP
typedef int (*WS_CallbackSftpCommand)(const char* in, char* out, int outSz);
#endif

typedef struct ssh_func_args {
    int    argc;
    char** argv;
    int    return_code;
    tcp_ready* signal;
    WS_CallbackUserAuth user_auth;
#ifdef WOLFSSH_SFTP
    /* callback for example sftp client commands instead of WFGETS */
    WS_CallbackSftpCommand sftp_cb;
#endif
} ssh_func_args;


#ifdef WOLFSSH_TEST_LOCKING

static INLINE void InitTcpReady(tcp_ready* ready)
{
    ready->ready = 0;
    ready->port = 0;
    ready->srfName = NULL;
#ifdef SINGLE_THREADED
#elif defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_mutex_init(&ready->mutex, 0);
    pthread_cond_init(&ready->cond, 0);
#endif
}


static INLINE void FreeTcpReady(tcp_ready* ready)
{
#ifdef SINGLE_THREADED
    (void)ready;
#elif defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_mutex_destroy(&ready->mutex);
    pthread_cond_destroy(&ready->cond);
#else
    (void)ready;
#endif
}


static INLINE void WaitTcpReady(ssh_func_args* args)
{
#if defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_mutex_lock(&args->signal->mutex);

    if (!args->signal->ready)
        pthread_cond_wait(&args->signal->cond, &args->signal->mutex);
    args->signal->ready = 0; /* reset */

    pthread_mutex_unlock(&args->signal->mutex);
#else
    (void)args;
#endif
}


#endif /* WOLFSSH_TEST_LOCKING */


#ifdef WOLFSSH_TEST_THREADING

typedef THREAD_RETURN WOLFSSH_THREAD THREAD_FUNC(void*);


static INLINE void ThreadStart(THREAD_FUNC fun, void* args, THREAD_TYPE* thread)
{
#ifdef SINGLE_THREADED
    (void)fun;
    (void)args;
    (void)thread;
#elif defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_create(thread, 0, fun, args);
    return;
#elif defined(WOLFSSL_TIRTOS)
    /* Initialize the defaults and set the parameters. */
    Task_Params taskParams;
    Task_Params_init(&taskParams);
    taskParams.arg0 = (UArg)args;
    taskParams.stackSize = 65535;
    *thread = Task_create((Task_FuncPtr)fun, &taskParams, NULL);
    if (*thread == NULL) {
        printf("Failed to create new Task\n");
    }
    Task_yield();
#else
    *thread = (THREAD_TYPE)_beginthreadex(0, 0, fun, args, 0, 0);
#endif
}


static INLINE void ThreadJoin(THREAD_TYPE thread)
{
#ifdef SINGLE_THREADED
    (void)thread;
#elif defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_join(thread, 0);
#elif defined(WOLFSSL_TIRTOS)
    while(1) {
        if (Task_getMode(thread) == Task_Mode_TERMINATED) {
            Task_sleep(5);
            break;
        }
        Task_yield();
    }
#else
    int res = WaitForSingleObject((HANDLE)thread, INFINITE);
    assert(res == WAIT_OBJECT_0);
    res = CloseHandle((HANDLE)thread);
    assert(res);
    (void)res; /* Suppress un-used variable warning */
#endif
}


static INLINE void ThreadDetach(THREAD_TYPE thread)
{
#ifdef SINGLE_THREADED
    (void)thread;
#elif defined(_POSIX_THREADS) && !defined(__MINGW32__)
    pthread_detach(thread);
#elif defined(WOLFSSL_TIRTOS)
#if 0
    while(1) {
        if (Task_getMode(thread) == Task_Mode_TERMINATED) {
            Task_sleep(5);
            break;
        }
        Task_yield();
    }
#endif
#else
    int res = CloseHandle((HANDLE)thread);
    assert(res);
    (void)res; /* Suppress un-used variable warning */
#endif
}

#endif /* WOLFSSH_TEST_THREADING */

#endif /* _WOLFSSH_TEST_H_ */
