Skip to content
Snippets Groups Projects
dap_common.c 17.92 KiB
/*
 * Authors:
 * Dmitriy A. Gearasimov <kahovski@gmail.com>
 * DeM Labs Inc.   https://demlabs.net
 * DeM Labs Open source community https://github.com/demlabsinc
 * Copyright  (c) 2017-2018
 * All rights reserved.

 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/>.
*/

#ifdef DAP_OS_ANDROID
#include <android/log.h>
#endif

#ifndef _WIN32
#include <pthread.h>
#include <syslog.h>

// Quick and dirty, I'm not sure but afair somewhere it was in UNIX systems too
#define min(a,b) (((a)<(b))?(a):(b))
#define max(a,b) (((a)>(b))?(a):(b))

#else
#include <stdlib.h>
#include <windows.h>
#include <process.h>
typedef HANDLE pthread_mutex_t;
#define popen _popen
#define pclose _pclose
#define pipe(pfds) _pipe(pfds, 4096, 0x8000)
#define PTHREAD_MUTEX_INITIALIZER 0
int pthread_mutex_lock(HANDLE **obj)
{
    return (( *obj = (HANDLE) CreateMutex(0, 1, 0) ) == NULL) ? 0 : 1;
}
int pthread_mutex_unlock(HANDLE *obj) {
    return (ReleaseMutex(obj) == 0) ? 0 : 1;
}
#endif
#include <time.h> /* 'nanosleep' */
#include <unistd.h> /* 'pipe', 'read', 'write' */
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "dap_common.h"
#include "dap_strfuncs.h"
#include "dap_string.h"
#include "dap_list.h"
#define LAST_ERROR_MAX 255

#define LOG_TAG "dap_common"

static char s_last_error[LAST_ERROR_MAX] = {0};
static enum dap_log_level dap_log_level = L_DEBUG;
static FILE * s_log_file = NULL;

static char log_tag_fmt_str[10];

/**
 * @brief set_log_level Sets the logging level
 * @param[in] ll logging level
 */
void dap_log_level_set(enum dap_log_level ll) {
    dap_log_level = ll;
}

enum dap_log_level dap_log_level_get(void) {
    return dap_log_level ;
}

/**
 * @brief dap_set_log_tag_width Sets the length of the label
 * @param[in] width Length not more than 99
 */
void dap_set_log_tag_width(size_t width) {
    if (width > 99) {
        fprintf(stderr,"Can't set width %zd", width);
        return;
    }

    // construct new log_tag_fmt_str
    strcpy(log_tag_fmt_str, "[%");
    strcat(log_tag_fmt_str, dap_itoa((int)width));
    strcat(log_tag_fmt_str, "s]\t");
}

/**
 * @brief dap_common_init initialise
 * @param[in] a_log_file
 * @return
 */
int dap_common_init(const char * a_log_file)
{
    srand((unsigned int)time(NULL));
    // init default log tag 8 width
    strcpy(log_tag_fmt_str, "[%8s]\t");

    if (a_log_file) {
        s_log_file = fopen(a_log_file , "a");
        if(s_log_file == NULL) {
            fprintf(stderr,"Can't open log file %s to append\n", a_log_file);
            return -1;
        }
    }

    // Set max items in log list
    dap_log_set_max_item(10);

    return 0;
}

/**
 * @brief dap_common_deinit Deinitialise
 */
void dap_common_deinit()
{
    if(s_log_file) fclose(s_log_file);
}

// list of logs
static dap_list_t *s_list_logs = NULL;
// for separate access to logs
static pthread_mutex_t s_list_logs_mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned int s_max_items = 1000;

/*
 * Set max items in log list
 */
void dap_log_set_max_item(unsigned int a_max)
{
    if(a_max>0)
        s_max_items = a_max;
}

/*
 * Get logs from list
 */
char *dap_log_get_item(time_t a_start_time, int a_limit)
{
    int l_count = 0;
    pthread_mutex_lock(&s_list_logs_mutex);
    dap_list_t *l_list = s_list_logs;

    l_list = dap_list_last(l_list);
    // find first item
    while(l_list) {
        dap_list_logs_item_t *l_item = (dap_list_logs_item_t*) l_list->data;
        if(a_start_time > l_item->t) {
            l_list = dap_list_next(l_list);
            break;
        }
        l_count++;
        l_list = dap_list_previous(l_list);
    }
    // no new logs
    if(!l_count){
        pthread_mutex_unlock(&s_list_logs_mutex);
        return NULL;
    }
    // if need all list
    if(!l_list)
        l_list = s_list_logs;

    // create return string
    dap_string_t *l_string = dap_string_new("");
    l_count = 0;
    while(l_list && a_limit > l_count) {
        dap_list_logs_item_t *l_item = (dap_list_logs_item_t*) l_list->data;
        //dap_string_append_printf(l_string, "%lld;%s\n", (int64_t) l_item->t, l_item->str);
        l_list = dap_list_next(l_list);
        l_count++;
        if(l_list && a_limit > l_count)
        	dap_string_append_printf(l_string, "%s\n", l_item->str);
        else// last item
        	dap_string_append_printf(l_string, "%s", l_item->str);
    }
    pthread_mutex_unlock(&s_list_logs_mutex);

    char *l_ret_str = dap_string_free(l_string, false);
    return l_ret_str;
}

// save log to list
static void log_add_to_list(time_t a_t, const char *a_time_str, const char * a_log_tag, enum dap_log_level a_ll,
        const char * a_format, va_list a_ap)
{
    pthread_mutex_lock(&s_list_logs_mutex);
    dap_string_t *l_string = dap_string_new("");
    dap_string_append_printf(l_string, "[%s]\t", a_time_str);
    if(a_ll == L_DEBUG) {
        l_string = dap_string_append(l_string, "[DBG]\t");
    } else if(a_ll == L_INFO) {
        l_string = dap_string_append(l_string, "[INF]\t");
    } else if(a_ll == L_NOTICE) {
        l_string = dap_string_append(l_string, "[ * ]\t");
    } else if(a_ll == L_WARNING) {
        l_string = dap_string_append(l_string, "[WRN]\t");
    } else if(a_ll == L_ERROR) {
        l_string = dap_string_append(l_string, "[ERR]\t");
    } else if(a_ll == L_CRITICAL) {
        l_string = dap_string_append(l_string, "[!!!]\t");
    }

    if(a_log_tag != NULL) {
        dap_string_append_printf(l_string, log_tag_fmt_str, a_log_tag);
    }
    dap_string_append_vprintf(l_string, a_format, a_ap);

    dap_list_logs_item_t *l_item = DAP_NEW(dap_list_logs_item_t);
    l_item->t = a_t;
    l_item->str = dap_string_free(l_string, false);
    s_list_logs = dap_list_append(s_list_logs, l_item);

    // remove old items
    unsigned int l_count = dap_list_length(s_list_logs);
    if(l_count > s_max_items) {
        // remove items from the beginning
        for(unsigned int i = 0; i < l_count - s_max_items; i++) {
            s_list_logs = dap_list_remove(s_list_logs, s_list_logs->data);
        }
    }
    pthread_mutex_unlock(&s_list_logs_mutex);
}

/**
 * @brief _log_it Writes information to the log
 * @param[in] log_tag Tag
 * @param[in] ll Log level
 * @param[in] format
 */
void _log_it(const char * log_tag,enum dap_log_level ll, const char * format,...)
{
    if(ll<dap_log_level)
        return;

    va_list ap;

    va_start(ap,format);
    _vlog_it(log_tag,ll, format,ap);
    va_end(ap);
}

void _vlog_it(const char * log_tag,enum dap_log_level ll, const char * format,va_list ap)
{
    va_list ap2,ap3;

    static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&mutex);
#ifdef DAP_OS_ANDROID
    char buf[4096];
    vsnprintf(buf,sizeof(buf),format,ap);
    switch (ll) {
    case L_INFO:
        __android_log_write(ANDROID_LOG_INFO,DAP_BRAND,buf);
        break;
    case L_WARNING:
        __android_log_write(ANDROID_LOG_WARN,DAP_BRAND,buf);
        break;
    case L_ERROR:
        __android_log_write(ANDROID_LOG_ERROR,DAP_BRAND,buf);
        break;
    case L_CRITICAL:
        __android_log_write(ANDROID_LOG_FATAL,DAP_BRAND,buf);
        abort();
        break;
    case L_DEBUG:
    default:
        __android_log_write(ANDROID_LOG_DEBUG,DAP_BRAND,buf);
    }
#endif


    va_copy(ap2,ap);
    va_copy(ap3,ap);
    time_t t=time(NULL);
    struct tm* tmp=localtime(&t);
    static char s_time[1024]={0};
    strftime(s_time,sizeof(s_time),"%x-%X",tmp);

    if (s_log_file ) fprintf(s_log_file,"[%s] ",s_time);
    printf("[%s] ",s_time);

    if(ll==L_DEBUG){
        if (s_log_file ) fprintf(s_log_file,"[DBG] ");
        printf(	"\x1b[37;2m[DBG] ");
    }else if(ll==L_INFO){
        if (s_log_file ) fprintf(s_log_file,"[INF] ");
        printf("\x1b[32;2m[INF] ");
    }else if(ll==L_NOTICE){
        if (s_log_file ) fprintf(s_log_file,"[ * ] ");
        printf("\x1b[32m[ * ] ");
    }else if(ll==L_WARNING){
        if (s_log_file ) fprintf(s_log_file,"[WRN] ");
        printf("\x1b[31;2m[WRN] ");
    }else if(ll==L_ERROR){
        if (s_log_file ) fprintf(s_log_file,"[ERR] ");
        printf("\x1b[31m[ERR] ");
    }else if(ll==L_CRITICAL){
        if (s_log_file ) fprintf(s_log_file,"[!!!] ");
        printf("\x1b[1;5;31m[!!!] ");
    }
    if(log_tag != NULL) {
        if (s_log_file ) fprintf(s_log_file,log_tag_fmt_str,log_tag);
        printf(log_tag_fmt_str,log_tag);
    }
    if (s_log_file ) vfprintf(s_log_file,format,ap);
    vprintf(format,ap2);
    if (s_log_file ) fprintf(s_log_file,"\n");
    printf("\x1b[0m\n");
    va_end(ap2);

    // save log to list
    log_add_to_list(t, s_time, log_tag, ll, format, ap3);
    va_end(ap3);

    if (s_log_file ) fflush(s_log_file);
    fflush(stdout);
    pthread_mutex_unlock(&mutex);
}

/**
 * @brief log_error Error log
 * @return
 */
const char * log_error()
{
    return s_last_error;
}


#define INT_DIGITS 19		/* enough for 64 bit integer */

/**
 * @brief itoa  The function converts an integer num to a string equivalent and places the result in a string
 * @param[in] i number
 * @return
 */
char *dap_itoa(int i)
{
    /* Room for INT_DIGITS digits, - and '\0' */
    static char buf[INT_DIGITS + 2];
    char *p = buf + INT_DIGITS + 1;	/* points to terminating '\0' */
    if (i >= 0) {
        do {
            *--p = '0' + (i % 10);
            i /= 10;
        } while (i != 0);
        return p;
    }
    else {			/* i < 0 */
        do {
            *--p = '0' - (i % 10);
            i /= 10;
        } while (i != 0);
        *--p = '-';
    }
    return p;
}


/**
 * @brief time_to_rfc822 Convert time_t to string with RFC822 formatted date and time
 * @param[out] out Output buffer
 * @param[out] out_size_mac Maximum size of output buffer
 * @param[in] t UNIX time
 * @return Length of resulting string if ok or lesser than zero if not
 */
int dap_time_to_str_rfc822(char * out, size_t out_size_max, time_t t)
{
    struct tm *tmp;
    tmp=localtime(&t);
    if(tmp== NULL){
        log_it(L_ERROR,"Can't convert data from unix fromat to structured one");
        return -2;
    }else{
        int ret;
        ret=strftime(out, out_size_max,"%a, %d %b %y %T %z",tmp);
        //free(tmp);
        if(ret>0){
            return ret;
        }else{
            log_it(L_ERROR,"Can't print formatted time in string");
            return -1;
        }
    }
}

static int breaker_set[2] = { -1, -1 };
static int initialized = 0;
static struct timespec break_latency = {0, 1 * 1000 * 1000 };

int get_select_breaker()
{
    if (!initialized)
    {
        if (pipe(breaker_set) < 0) return -1;
        else initialized = 1;
    }

    return breaker_set[0];
}
int send_select_break()
{
    if (!initialized) return -1;
    char buffer[1];
    if (write(breaker_set[1], "\0", 1) <= 0) return -1;
    nanosleep(&break_latency, NULL);
    if (read(breaker_set[0], buffer, 1) <= 0 || buffer[0] != '\0') return -1;
    return 0;
}

#ifdef ANDROID1
static u_long myNextRandom = 1;

double atof(const char *nptr)
{
    return (strtod(nptr, NULL));
}

int rand(void)
{
    return (int)((myNextRandom = (1103515245 * myNextRandom) + 12345) % ((u_long)RAND_MAX + 1));
}

void srand(u_int seed)
{
    myNextRandom = seed;
}

#endif

/**
 * @brief exec_with_ret Executes a command with result return
 * @param[in] a_cmd Command
 * @return Result
 */
char * exec_with_ret(const char * a_cmd)
{
    FILE * fp;
    size_t buf_len = 0;
    char buf[4096] = {0};
    fp= popen(a_cmd, "r");
    if (!fp) {
        goto FIN;
    }
    memset(buf,0,sizeof(buf));
    fgets(buf,sizeof(buf)-1,fp);
    pclose(fp);
    buf_len=strlen(buf);
    if(buf[buf_len-1] =='\n')buf[buf_len-1] ='\0';
FIN:
    return strdup(buf);
}

/**
 * @brief exec_with_ret_multistring performs a command with a result return in the form of a multistring
 * @param[in] a_cmd Coomand
 * @return Return
 */
char * exec_with_ret_multistring(const char * a_cmd)
{
    FILE * fp;
    size_t buf_len = 0;
    char buf[4096] = {0};
    fp= popen(a_cmd, "r");
    if (!fp) {
        goto FIN;
    }
    memset(buf,0,sizeof(buf));
    char retbuf[4096] = {0};
    while(fgets(buf,sizeof(buf)-1,fp)) {
        strcat(retbuf, buf);
    }
    pclose(fp);
    buf_len=strlen(retbuf);
    if(retbuf[buf_len-1] =='\n')retbuf[buf_len-1] ='\0';
FIN:
    return strdup(retbuf);
}

static const char l_possible_chars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

/**
 * @brief random_string_fill Filling a string with random characters
 * @param[out] str A pointer to a char array
 * @param[in] length The length of the array or string
 */
void dap_random_string_fill(char *str, size_t length) {
    for(size_t i = 0; i < length; i++)
        str[i] = l_possible_chars[
                rand() % (sizeof(l_possible_chars) - 1)];
}

/**
 * @brief random_string_create Generates a random string
 * @param[in] a_length lenght
 * @return a pointer to an array
 */
char * dap_random_string_create_alloc(size_t a_length)
{
    char * ret = DAP_NEW_SIZE(char, a_length+1);
    size_t i;
    for(i=0; i<a_length; ++i) {
        int index = rand() % (sizeof(l_possible_chars)-1);
        ret[i] = l_possible_chars[index];
    }
    return ret;
}


#define MAX_PRINT_WIDTH 100

static void _printrepchar(char c, size_t count) {
    assert(count < MAX_PRINT_WIDTH &&
           "Too many characters");
    static char buff[MAX_PRINT_WIDTH];
    memset(buff, (int)c, count);
    printf("%s\n", buff);
}


/**
 * @brief The function displays a dump
 * @param[in] data The data dump you want to display
 * @param[in] size The size of the data whose dump you want to display
 *
 * The function displays a dump, for example an array, in hex format
*/
void dap_dump_hex(const void* data, size_t size) {
    char ascii[17];
    size_t i, j;
    ascii[16] = '\0';
    for (i = 0; i < size; ++i) {
        printf("%02X ", ((const unsigned char*)data)[i]);
        if (((const unsigned char*)data)[i] >= ' ' && ((const unsigned char*)data)[i] <= '~') {
            ascii[i % 16] = ((const char*)data)[i];
        } else {
            ascii[i % 16] = '.';
        }
        if ((i+1) % 8 == 0 || i+1 == size) {
            printf(" ");
            if ((i+1) % 16 == 0) {
                printf("|  %s \n", ascii);
            } else if (i+1 == size) {
                ascii[(i+1) % 16] = '\0';
                if ((i+1) % 16 <= 8) {
                    printf(" ");
                }
                for (j = (i+1) % 16; j < 16; ++j) {
                    printf("   ");
                }
                printf("|  %s \n", ascii);
            }
        }
    }
    _printrepchar('-', 70);
}

void *memzero(void *a_buf, size_t n)
{
    memset(a_buf,0,n);
    return a_buf;
}

/**
 * Convert binary data to binhex encoded data.
 *
 * out output buffer, must be twice the number of bytes to encode.
 * len is the size of the data in the in[] buffer to encode.
 * return the number of bytes encoded, or -1 on error.
 */
size_t dap_bin2hex(char *a_out, const void *a_in, size_t a_len)
{
    size_t ct = a_len;
    static char hex[] = "0123456789ABCDEF";
    const uint8_t *l_in = (const uint8_t *)a_in;
    if(!a_in || !a_out )
        return 0;
    // hexadecimal lookup table
    while(ct-- > 0){
        *a_out++ = hex[*l_in >> 4];
        *a_out++ = hex[*l_in++ & 0x0F];
    }
    return a_len;
}

/**
 * Convert binhex encoded data to binary data
 *
 * len is the size of the data in the in[] buffer to decode, and must be even.
 * out outputbuffer must be at least half of "len" in size.
 * The buffers in[] and out[] can be the same to allow in-place decoding.
 * return the number of bytes encoded, or 0 on error.
 */
size_t dap_hex2bin(uint8_t *a_out, const char *a_in, size_t a_len)
{
    // '0'-'9' = 0x30-0x39
    // 'a'-'f' = 0x61-0x66
    // 'A'-'F' = 0x41-0x46
    size_t ct = a_len;
    if(!a_in || !a_out || (a_len & 1))
        return 0;
    while(ct > 0) {
        char ch1 = ((*a_in >= 'a') ? (*a_in++ - 'a' + 10) : ((*a_in >= 'A') ? (*a_in++ - 'A' + 10) : (*a_in++ - '0'))) << 4;
        char ch2 = ((*a_in >= 'a') ? (*a_in++ - 'a' + 10) : ((*a_in >= 'A') ? (*a_in++ - 'A' + 10) : (*a_in++ - '0'))); // ((*in >= 'A') ? (*in++ - 'A' + 10) : (*in++ - '0'));
        *a_out++ =(uint8_t) ch1 + (uint8_t) ch2;
        ct -= 2;
    }
    return a_len;
}

/**
 * Convert string to digit
 */
void dap_digit_from_string(const char *num_str, uint8_t *raw, size_t raw_len)
{
    if(!num_str)
        return;
    uint64_t val;
    if(!strncasecmp(num_str, "0x", 2)) {
        val = strtoull(num_str + 2, NULL, 16);
    }else {
        val = strtoull(num_str, NULL, 10);
    }
    // for LITTLE_ENDIAN (Intel), do nothing, otherwise swap bytes
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    val = le64toh(val);
#endif
    memset(raw, 0, raw_len);
    memcpy(raw, &val, min(raw_len, sizeof(uint64_t)));
}


#ifdef __MINGW32__
/*!
 * \brief Execute shell command silently
 * \param a_cmd command line
 * \return 0 if success, -1 otherwise
 */
int exec_silent(const char * a_cmd) {
    PROCESS_INFORMATION p_info;
    STARTUPINFOA s_info;
    memzero(&s_info, sizeof(s_info));
    memzero(&p_info, sizeof(p_info));

    s_info.cb = sizeof(s_info);
    char cmdline[512] = {'\0'};
    strcat(cmdline, "C:\\Windows\\System32\\cmd.exe /c ");
    strcat(cmdline, a_cmd);

    if (CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0x08000000, NULL, NULL, &s_info, &p_info)) {
        WaitForSingleObject(p_info.hProcess, 0xffffffff);
        CloseHandle(p_info.hProcess);
        CloseHandle(p_info.hThread);
        return 0;
    }
    else {
        return -1;
    }
}
#endif