Skip to content
Snippets Groups Projects
dap_chain_node_cli.c 18.6 KiB
Newer Older
/*
 * Authors:
 * Dmitriy A. Gearasimov <naeper@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net

 This file is part of DAP (Deus Applications Prototypes) the open source project

 DAP (Deus Applicaions Prototypes) 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.

 DAP 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 any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
#include <assert.h>
//#include <glib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h> // for close
#include <fcntl.h>
//#include <sys/poll.h>
//#include <sys/select.h>
#include <netinet/in.h>
#include <sys/un.h>
#define closesocket close
typedef int SOCKET;
#define SOCKET_ERROR    -1  // for win32 =  (-1)
#define INVALID_SOCKET  -1  // for win32 =  (SOCKET)(~0)
// for Windows
#else
#include <winsock2.h>
#include <WS2tcpip.h>
#endif

#include "iputils/iputils.h"
#include "dap_strfuncs.h"
#include "dap_list.h"
#include "dap_chain_node_cli_cmd.h"
Dmitriy A. Gerasimov's avatar
Dmitriy A. Gerasimov committed
#include "dap_chain_node_client.h"
#include "dap_chain_node_cli.h"

//#include "dap_chain_node_cli.h"

#define LOG_TAG "chain_node_cli"

static SOCKET server_sockfd = -1;
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
static dap_chain_node_cmd_item_t * s_commands = NULL;
/**
 * Wait for data
 * timeout -  timeout in ms
 * [Specifying a negative value in timeout means an infinite timeout.]
 * [Specifying a timeout of zero causes poll() to return immediately, even if no file descriptors are ready.]
 * return zero if the time limit expired
 * return: >0 if data is present to read
 * return: -1 if error
 */
static int s_poll(int socket, int timeout)
    // POLLIN - received data
    // POLLNVAL - closed the socket on our side
    // POLLHUP - closed the socket on another side (does not work! Received POLLIN and the next reading returns 0 bytes)
    fds.events = POLLIN; // | | POLLNVAL | POLLHUP | POLLERR | POLLPRI
    res = poll(&fds, 1, timeout);

    // since POLLIN=(POLLRDNORM | POLLRDBAND), then maybe revents=POLLRDNORM
    if(res == 1 && !(fds.revents & POLLIN)) //if(res==1 && fds.revents!=POLLIN)
        return -1;
    return res;
}

/**
 * Check socket for validity
 */
static bool is_valid_socket(SOCKET sock)
{
    struct pollfd fds;
    fds.fd = sock;
    fds.events = POLLIN;
    // return: -1 err, 0 timeout, 1 waited
    int count_desc = poll(&fds, 1, 0);
    // event with an error code
        // feature of disconnection under Windows
        // under Windows, with socket closed fds.revents=POLLHUP, in Unix fds.events = POLLIN
        if(fds.revents & (POLLERR | POLLHUP | POLLNVAL))
            return false;
        // feature of disconnection under Unix (QNX)
        // under Windows, with socket closed res = 0, in Unix res = -1
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        long res = recv(sock, buf, 1, MSG_PEEK); // MSG_PEEK  The data is treated as unread and the next recv() function shall still return this data.
        // data in the buffer must be(count_desc>0), but read 0 bytes(res=0)
        if(!res && (fds.revents & POLLIN))
            return false;
    }
    return true;
}

/**
 * Read from socket
 *
 * timeout in milliseconds
 * return the number of read bytes (-1 err or -2 timeout)
 */
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
long s_recv(SOCKET sock, unsigned char *buf, size_t bufsize, int timeout)
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    long res;
    fds.fd = sock;
    fds.events = POLLIN; // | POLLNVAL | POLLHUP | POLLERR | POLLPRI;// | POLLRDNORM;//POLLOUT |
    res = poll(&fds, 1, timeout);
    if(res == 1 && !(fds.revents & POLLIN))
        return -1;
    if(!res) // timeout
        return -2;
    if(res < 1) {
        return -1;
    }
    //    res = read(sock, (char*) buf, bufsize);
    res = recv(sock, (char*) buf, bufsize, 0); //MSG_WAITALL
    if(res <= 0) { //EINTR=4  ENOENT=2 EINVAL=22 ECONNRESET=254
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        printf("[s_recv] recv()=%ld errno=%d\n", res, errno);
    }
    return res;
}

/**
 * Reading from the socket till arrival the specified string
 *
 * stop_str - string to which reading will continue
 * del_stop_str - удалять ли строку для поиска в конце
 * timeout - in ms
 * return: string (if waited for final characters) or NULL, if the string requires deletion
 */
char* s_get_next_str(SOCKET nSocket, int *dwLen, const char *stop_str, bool del_stop_str, int timeout)
{
    bool bSuccess = false;
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    long nRecv = 0; // count of bytes received
    size_t stop_str_len = (stop_str) ? strlen(stop_str) : 0;
    // if there is nothing to look for
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    size_t lpszBuffer_len = 256;
    char *lpszBuffer = DAP_NEW_Z_SIZE(char, lpszBuffer_len);
    // received string will not be larger than MAX_REPLY_LEN
        // read one byte
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        long ret = s_recv(nSocket, (unsigned char *) (lpszBuffer + nRecv), 1, timeout);
        //int ret = recv(nSocket,lpszBuffer+nRecv,1, 0);
        //printf("**debug** socket=%d read  %d bytes '%0s'",nSocket, ret, (lpszBuffer + nRecv));
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        while((nRecv + 1) >= (long) lpszBuffer_len)
        {
            lpszBuffer_len *= 2;
            lpszBuffer = (char*) realloc(lpszBuffer, lpszBuffer_len);
        }
        // search for the required string
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        if(nRecv >=  (long) stop_str_len) {
            // found the required string
            if(!strncasecmp(lpszBuffer + nRecv - stop_str_len, stop_str, stop_str_len)) {
                bSuccess = true;
                break;
            }
        }
    };
    // end reading
        // delete the searched string
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
            lpszBuffer[nRecv -  (long) stop_str_len] = '\0';
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
                *dwLen =(int) nRecv - (int) stop_str_len;
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
                *dwLen = (int) nRecv;
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
        lpszBuffer = DAP_REALLOC(lpszBuffer,(size_t) *dwLen + 1);
    // in case of an error or missing string
/**
 * threading function for processing a request from a client
 */
static void* thread_one_client_func(void *args)
{
    SOCKET newsockfd = (SOCKET) (intptr_t) args;
    log_it(L_INFO, "new connection sockfd=%d", newsockfd);

    int str_len, marker = 0;
    int timeout = 5000; // 5 sec
    char **argv = NULL;
    int argc = 0;
    dap_list_t *cmd_param_list = NULL;
        // wait data from client
        int is_data = s_poll(newsockfd, timeout);
        // timeout
        // error (may be socket closed)

        int is_valid = is_valid_socket(newsockfd);
        if(!is_valid)
        {
            break;
        }
        // receiving http header
        char *str_header = s_get_next_str(newsockfd, &str_len, "\r\n", true, timeout);
        // bad format
        if(!str_header)
            break;
        if(str_header && strlen(str_header) == 0) {
            marker++;
            if(marker == 1)
                continue;
        }
        // filling parameters of command
            cmd_param_list = dap_list_append(cmd_param_list, str_header);
            //printf("g_list_append argc=%d command=%s ", argc, str_header);
            argc++;
        }
        else
            free(str_header);
        if(marker == 2) {
            dap_list_t *list = cmd_param_list;
            unsigned int argc = dap_list_length(list);
            if(argc >= 1) {
                list = dap_list_next(list);
                char *str_cmd = dap_strdup_printf("%s", cmd_name);
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
                dap_chain_node_cmd_item_t *l_cmd = dap_chain_node_cli_cmd_find(cmd_name);
                int res = -1;
                char *str_reply = NULL;
                if(l_cmd){
                    while(list) {
                        str_cmd = dap_strdup_printf("%s;%s", str_cmd, list->data);
                        list = dap_list_next(list);
                    }
                    log_it(L_INFO, "execute command=%s", str_cmd);
                    // exec command
                    char **argv = dap_strsplit(str_cmd, ";", -1);
                    // Call the command function
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
                    if(l_cmd && l_cmd->func)
                        res = (*(l_cmd->func))(argc, (const char **) argv, &str_reply);
                    else {
                        log_it(L_WARNING,"No function for command \"%s\" but it registred?!", str_cmd);
                    }
                    dap_strfreev(argv);
                    str_reply = dap_strdup_printf("can't recognize command=%s", str_cmd);
                    log_it(L_ERROR, str_reply);
                char *reply_body = dap_strdup_printf("%d\r\n%s\r\n", res, (str_reply) ? str_reply : "");
                // return the result of the command function
                char *reply_str = dap_strdup_printf("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s",
                        strlen(reply_body), reply_body);
                int ret = send(newsockfd, reply_str, strlen(reply_str) + 1, 1000);
                DAP_DELETE(str_reply);
                DAP_DELETE(reply_str);
                DAP_DELETE(reply_body);
                DAP_DELETE(str_cmd);
            dap_list_free_full(cmd_param_list, free);
    // close connection
    int cs = closesocket(newsockfd);
    log_it(L_INFO, "close connection=%d sockfd=%d", cs, newsockfd);
    return NULL;
}

/**
 * main threading server function
 */
static void* thread_main_func(void *args)
{
    SOCKET sockfd = (SOCKET) (intptr_t) args;
    SOCKET newsockfd;
    log_it(L_INFO, "Server start socket=%s", UNIX_SOCKET_FILE);
    // wait of clients
    while(1)
    {
        pthread_t threadId;
        struct sockaddr_in peer;
        socklen_t size = sizeof(peer);
        // received a new connection request
        if((newsockfd = accept(sockfd, (struct sockaddr*) &peer, &size)) == (SOCKET) -1) {
            log_it(L_ERROR, "new connection break newsockfd=%d", newsockfd);
            break;
        }
        // create child thread for a client connection
        pthread_create(&threadId, NULL, thread_one_client_func, (void*) (intptr_t) newsockfd);
        // in order to thread not remain in state "dead" after completion
    // close connection
    int cs = closesocket(sockfd);
    log_it(L_INFO, "Exit server thread=%d socket=%d", cs, sockfd);
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
/**
 * Write text to reply string
 */
void dap_chain_node_cli_set_reply_text(char **str_reply, const char *str, ...)
{
    if(str_reply) {
        if(*str_reply) {
            assert(!*str_reply);
            DAP_DELETE(*str_reply);
            *str_reply = NULL;
        }
        va_list args;
        va_start(args, str);
        *str_reply = dap_strdup_vprintf(str, args); //*str_reply = dap_strdup(str);
        va_end(args);
    }
}

/**
 * find option value
 *
 * return index of string in argv, or 0 if not found
 */
int dap_chain_node_cli_find_option_val(const char** argv, int arg_start, int arg_end, const char *opt_name, const char **opt_value)
{
    int arg_index = arg_start;
    const char *arg_string;

    while(arg_index < arg_end)
    {
        arg_string = argv[arg_index];
        // find opt_name
        if(arg_string && opt_name && !strcmp(arg_string, opt_name)) {
            // find opt_value
            if(opt_value) {
                arg_string = argv[++arg_index];
                if(arg_string) {
                    *opt_value = arg_string;
                    return arg_index;
                }
            }
            else
                // need only opt_name
                return arg_index;
        }
        arg_index++;
    }
    return 0;
}

/**
 * @brief s_cmd_item_create
 * @param a_name
 * @param func
 * @param doc
 * @param doc_ex
 * @return
 */
void dap_chain_node_cli_cmd_item_create(const char * a_name, cmdfunc_t *a_func, const char *a_doc, const char *a_doc_ex)
{
    dap_chain_node_cmd_item_t *l_cmd_item = DAP_NEW_Z(dap_chain_node_cmd_item_t);
    snprintf(l_cmd_item->name,sizeof (l_cmd_item->name),"%s",a_name);
    l_cmd_item->doc = strdup( a_doc);
    l_cmd_item->doc_ex = strdup( a_doc_ex);
    l_cmd_item->func = a_func;
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    HASH_ADD_STR(s_commands,name,l_cmd_item);
    log_it(L_DEBUG,"Added command %s",l_cmd_item->name);
}

/**
 * @brief dap_chain_node_cli_command_get_first
 * @return
 */
dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_get_first()
{
    return s_commands;
}

/**
 * @brief dap_chain_node_cli_command_find
 * @param a_name
 * @return
 */
dap_chain_node_cmd_item_t* dap_chain_node_cli_cmd_find(const char *a_name)
{
    dap_chain_node_cmd_item_t *l_cmd_item = NULL;
    HASH_FIND_STR(s_commands,a_name,l_cmd_item);
    return l_cmd_item;
}


/**
 * Initialization of the server side of the interaction
 * with the console kelvin-node-cli
 *
 * return 0 if OK, -1 error
 */
int dap_chain_node_cli_init(dap_config_t * g_config)
{
    struct sockaddr_un server = { AF_UNIX, UNIX_SOCKET_FILE };
    //server.sun_family = AF_UNIX;
    //strcpy(server.sun_path, SOCKET_FILE);
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("global_db", com_global_db, "Work with global database",
                                                   "global_db wallet_info set -addr <wallet address> -cell <cell id> \n\n"
                                                   "global_db cells add -cell <cell id> \n\n"
                                                   "global_db node add -addr {<node address> | -alias <node alias>} -cell <cell id>  {-ipv4 <ipv4 external address> | -ipv6 <ipv6 external address>}\n\n"
                                                            "global_db node del -addr <node address> | -alias <node alias>\n\n"
                                                            "global_db node link {add|del} {-addr <node address> | -alias <node alias>} -link <node address>\n\n"
                                                            "global_db node dump\n\n"
                                                            "global_db node dump -addr <node address> | -alias <node alias>\n\n"
                                                            "global_db node get\n\n"
                                                            "global_db node set -addr <node address> | -alias <node alias>\n\n"
                                                  "global_db node remote_set -addr <node address> | -alias <node alias>\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("node", com_node, "Work with node",
            "node alias {<node address> | -alias <node alias>}\n\n"
                    "node connect {<node address> | -alias <node alias>}\n\n"
                    "node handshake {<node address> | -alias <node alias>}\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("ping", com_ping, "Send ICMP ECHO_REQUEST to network hosts",
            "ping [-c <count>] host\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("traceroute", com_traceroute, "Print the hops and time of packets trace to network host",
            "traceroute host\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("tracepath", com_tracepath, "Traces path to a network host along this path",
            "tracepath host\n");
    dap_chain_node_cli_cmd_item_create ("help", com_help, "Description of command parameters\n", "");
    dap_chain_node_cli_cmd_item_create ("?", com_help, "Synonym for 'help'\n", "");
    dap_chain_node_cli_cmd_item_create ("wallet", com_tx_wallet, "Wallet info", "wallet [list | info -addr <addr> -w <wallet_name>]\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("token_emit", com_token_emit, "Token emission",
            "token_emit addr <addr> tokent <token> certs <cert> emission_value <val>\n");
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("tx_create", com_tx_create, "Make transaction",
            "tx_create from_wallet_name <name> to_addr <addr> token <token> value <val> [fee <addr> value_fee <val>]\n" );
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("tx_cond_create", com_tx_cond_create, "Make cond transaction",
            "tx_cond_create todo\n" );
dmitriy.gerasimov's avatar
dmitriy.gerasimov committed
    dap_chain_node_cli_cmd_item_create ("tx_verify", com_tx_verify, "Verifing transaction",
            "tx_verify  -wallet <wallet name> [-path <wallet path>]\n" );

    // init client for handshake

    SOCKET sockfd;

    if(server_sockfd >= 0) {
        dap_chain_node_cli_delete();
        server_sockfd = 0;
    }

    // create socket
    if((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == INVALID_SOCKET)
        return -1;
    int gdsg = sizeof(struct sockaddr_un);
    if(access( UNIX_SOCKET_FILE, R_OK) != -1)
            {
        unlink(UNIX_SOCKET_FILE);
    // connecting the address with a socket
    if(bind(sockfd, (const struct sockaddr*) &server, sizeof(struct sockaddr_un)) == SOCKET_ERROR) {
        // errno = EACCES  13  Permission denied
        if(errno == EACCES) // EACCES=13
            log_it(L_ERROR, "Server can't start(err=%d). Can't create file=%s [Permission denied]", errno,
                    UNIX_SOCKET_FILE);
        else
            log_it(L_ERROR, "Server can't start(err=%d). May be problem with file=%s?", errno, UNIX_SOCKET_FILE);
    // turn on reception of connections
    if(listen(sockfd, 5) == SOCKET_ERROR)
        return -1;
    // create thread for waiting of clients
    pthread_t threadId;
    if(pthread_create(&threadId, NULL, thread_main_func, (void*) (intptr_t) sockfd) != 0) {
        closesocket(sockfd);
        return -1;
    }
    // in order to thread not remain in state "dead" after completion
    pthread_detach(threadId);
    server_sockfd = sockfd;
    return 0;
}

/**
 * Deinitialization of the server side
 *
 */
void dap_chain_node_cli_delete(void)
{
    if(server_sockfd >= 0)
        closesocket(server_sockfd);

    // deinit client for handshake
Dmitriy A. Gerasimov's avatar
Dmitriy A. Gerasimov committed
    dap_chain_node_client_deinit();