/*****
*
* Copyright (C) 2003-2019 CS-SI. All Rights Reserved.
* Author: Nicolas Delon <nicolas.delon@prelude-ids.com>
*
* This file is part of the Prelude library.
*
* This program 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, or (at your option)
* any later version.
*
* This program 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.
*
*****/

#include "libmissing.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "prelude-error.h"
#include "prelude-inttypes.h"
#include "prelude-string.h"
#include "common.h"
#include "idmef-data.h"


/*
 * Data structure may be free'd
 */
#define IDMEF_DATA_OWN_STRUCTURE  0x1

/*
 * Data content may be free'd
 */
#define IDMEF_DATA_OWN_DATA       0x2


#define IDMEF_DATA_DECL(idmef_data_type, c_type, name, fname)         \
int idmef_data_new_ ## name(idmef_data_t **nd, c_type val)            \
{                                                                     \
        int ret;                                                      \
                                                                      \
        ret = idmef_data_new(nd);                                     \
        if ( ret < 0 )                                                \
                return ret;                                           \
                                                                      \
        idmef_data_set_ ## fname(*nd, val);                           \
                                                                      \
        return ret;                                                   \
}                                                                     \
                                                                      \
void idmef_data_set_ ## name(idmef_data_t *ptr, c_type val)           \
{                                                                     \
        prelude_return_if_fail(ptr);                                  \
        idmef_data_destroy_internal(ptr);                             \
        ptr->type = idmef_data_type;                                  \
        ptr->len = sizeof(val);                                       \
        ptr->data.fname ## _data = val;                               \
}                                                                     \
                                                                      \
c_type idmef_data_get_ ## name(const idmef_data_t *ptr)               \
{                                                                     \
        return ptr->data.fname ## _data;                              \
}


IDMEF_DATA_DECL(IDMEF_DATA_TYPE_CHAR, char, char, char)
IDMEF_DATA_DECL(IDMEF_DATA_TYPE_BYTE, uint8_t, byte, byte)
IDMEF_DATA_DECL(IDMEF_DATA_TYPE_INT, uint32_t, uint32, int)
IDMEF_DATA_DECL(IDMEF_DATA_TYPE_INT, uint64_t, uint64, int)
IDMEF_DATA_DECL(IDMEF_DATA_TYPE_INT, int64_t, int, int)
IDMEF_DATA_DECL(IDMEF_DATA_TYPE_FLOAT, float, float, float)


int idmef_data_new(idmef_data_t **data)
{
        *data = calloc(1, sizeof(**data));
        if ( ! *data )
                return prelude_error_from_errno(errno);

        (*data)->refcount = 1;
        (*data)->flags |= IDMEF_DATA_OWN_STRUCTURE;

        return 0;
}


int idmef_data_new_time(idmef_data_t **data, idmef_time_t *time)
{
        int ret;

        ret = idmef_data_new(data);
        if ( ret < 0 )
                return ret;

        (*data)->len = 1;
        (*data)->type = IDMEF_DATA_TYPE_TIME;
        (*data)->flags |= IDMEF_DATA_OWN_DATA;
        (*data)->data.ro_data = time;

        return 0;
}


void idmef_data_set_time(idmef_data_t *data, idmef_time_t *time)
{
        data->len = 1;
        data->type = IDMEF_DATA_TYPE_TIME;
        data->flags |= IDMEF_DATA_OWN_DATA;
        data->data.ro_data = time;
}

idmef_data_t *idmef_data_ref(idmef_data_t *data)
{
        prelude_return_val_if_fail(data, NULL);

        data->refcount++;
        return data;
}



int idmef_data_set_ptr_ref_fast(idmef_data_t *data, idmef_data_type_t type, const void *ptr, size_t len)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        idmef_data_destroy_internal(data);

        data->type = type;
        data->data.ro_data = ptr;
        data->len = len;

        return 0;
}



int idmef_data_set_ptr_dup_fast(idmef_data_t *data, idmef_data_type_t type, const void *ptr, size_t len)
{
        void *new;

        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        idmef_data_destroy_internal(data);

        new = malloc(len);
        if ( ! new )
                return -1;

        memcpy(new, ptr, len);

        data->type = type;
        data->data.rw_data = new;
        data->len = len;
        data->flags |= IDMEF_DATA_OWN_DATA;

        return 0;
}



int idmef_data_set_ptr_nodup_fast(idmef_data_t *data, idmef_data_type_t type, void *ptr, size_t len)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        idmef_data_destroy_internal(data);

        data->type = type;
        data->data.rw_data= ptr;
        data->len = len;
        data->flags |= IDMEF_DATA_OWN_DATA;

        return 0;
}



int idmef_data_new_ptr_ref_fast(idmef_data_t **data, idmef_data_type_t type, const void *ptr, size_t len)
{
        int ret;

        ret = idmef_data_new(data);
        if ( ret < 0 )
                return ret;

        ret = idmef_data_set_ptr_ref_fast(*data, type, ptr, len);
        if ( ret < 0 )
                idmef_data_destroy(*data);

        return ret;
}



int idmef_data_new_ptr_dup_fast(idmef_data_t **data, idmef_data_type_t type, const void *ptr, size_t len)
{
        int ret;

        ret = idmef_data_new(data);
        if ( ret < 0 )
                return ret;

        ret = idmef_data_set_ptr_dup_fast(*data, type, ptr, len);
        if ( ret < 0 )
                idmef_data_destroy(*data);

        return ret;
}



int idmef_data_new_ptr_nodup_fast(idmef_data_t **data, idmef_data_type_t type, void *ptr, size_t len)
{
        int ret;

        ret = idmef_data_new(data);
        if ( ret < 0 )
                return ret;

        ret = idmef_data_set_ptr_nodup_fast(*data, type, ptr, len);
        if ( ret < 0 )
                idmef_data_destroy(*data);

        return ret;
}




/**
 * idmef_data_copy_ref:
 * @src: Source #idmef_data_t object.
 * @dst: Destination #idmef_data_t object.
 *
 * Makes @dst reference the same buffer as @src.
 *
 * Returns: 0 on success, a negative value if an error occured.
 */
int idmef_data_copy_ref(const idmef_data_t *src, idmef_data_t *dst)
{
        prelude_return_val_if_fail(src, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(dst, prelude_error(PRELUDE_ERROR_ASSERTION));

        idmef_data_destroy_internal(dst);

        dst->type = src->type;
        dst->len = src->len;
        dst->data = src->data;
        dst->flags &= ~IDMEF_DATA_OWN_DATA;

        return 0;
}




/**
 * idmef_data_copy_dup:
 * @src: Source #idmef_data_t object.
 * @dst: Destination #idmef_data_t object.
 *
 * Copies @src to @dst, including the associated buffer.
 * This is an alternative to idmef_data_clone().
 *
 * Returns: 0 on success, a negative value if an error occured.
 */
int idmef_data_copy_dup(const idmef_data_t *src, idmef_data_t *dst)
{
        int ret;

        prelude_return_val_if_fail(src, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(dst, prelude_error(PRELUDE_ERROR_ASSERTION));

        idmef_data_destroy_internal(dst);

        dst->type = src->type;
        dst->flags |= IDMEF_DATA_OWN_DATA;
        dst->len = src->len;

        if ( src->type == IDMEF_DATA_TYPE_TIME ) {
                ret = idmef_time_clone((idmef_time_t *) src->data.rw_data, (idmef_time_t **) &dst->data.rw_data);
                if ( ret < 0 )
                        return ret;
        }

        else if ( src->type == IDMEF_DATA_TYPE_CHAR_STRING || src->type == IDMEF_DATA_TYPE_BYTE_STRING ) {
                dst->data.rw_data = malloc(src->len);
                if ( ! dst->data.rw_data )
                        return -1;

                memcpy(dst->data.rw_data, src->data.ro_data, src->len);
        } else {
                dst->data = src->data;
        }

        return 0;
}



int idmef_data_clone(const idmef_data_t *src, idmef_data_t **dst)
{
        int ret;

        prelude_return_val_if_fail(src, prelude_error(PRELUDE_ERROR_ASSERTION));

        ret = idmef_data_new(dst);
        if ( ret < 0 )
                return ret;

        ret = idmef_data_copy_dup(src, *dst);
        if ( ret < 0 )
                idmef_data_destroy(*dst);

        return ret;
}



const char *idmef_data_get_char_string(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, NULL);
        return data->data.ro_data;
}



const unsigned char *idmef_data_get_byte_string(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, NULL);
        return data->data.ro_data;
}



/**
 * idmef_data_get_type
 * @data: Pointer to an #idmef_data_t object.
 *
 * Returns: the type of the embedded data.
 */
idmef_data_type_t idmef_data_get_type(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        return data->type;
}




/**
 * idmef_data_get_len:
 * @data: Pointer to an #idmef_data_t object.
 *
 * Returns: the length of data contained within @data object.
 */
size_t idmef_data_get_len(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, 0);
        return data->len;
}




/**
 * idmef_data_get_data:
 * @data: Pointer to an #idmef_data_t object.
 *
 * Returns: the data contained within @data object.
 */
const void *idmef_data_get_data(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, NULL);

        switch ( data->type ) {
        case IDMEF_DATA_TYPE_UNKNOWN:
                return NULL;

        case IDMEF_DATA_TYPE_CHAR_STRING:
        case IDMEF_DATA_TYPE_BYTE_STRING:
        case IDMEF_DATA_TYPE_TIME:
                return data->data.ro_data;

        default:
                return &data->data;
        }

        return NULL;
}



/**
 * idmef_data_is_empty:
 * @data: Pointer to an #idmef_data_t object.
 *
 * Returns: TRUE if empty, FALSE otherwise.
 */
prelude_bool_t idmef_data_is_empty(const idmef_data_t *data)
{
        prelude_return_val_if_fail(data, TRUE);
        return (data->len == 0) ? TRUE : FALSE;
}



static int bytes_to_string(prelude_string_t *out, const unsigned char *src, size_t size)
{
        char c;
        int ret;
        static const char b64tbl[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

        while ( size ) {
                ret = prelude_string_ncat(out, &b64tbl[(src[0] >> 2) & 0x3f], 1);
                if ( ret < 0 )
                        return ret;

                c = b64tbl[((src[0] << 4) + ((--size) ? src[1] >> 4 : 0)) & 0x3f];

                ret = prelude_string_ncat(out, &c, 1);
                if ( ret < 0 )
                        return ret;

                c = (size) ? b64tbl[((src[1] << 2) + ((--size) ? src[2] >> 6 : 0)) & 0x3f] : '=';

                ret = prelude_string_ncat(out, &c, 1);
                if ( ret < 0 )
                        return ret;

                c = (size && size--) ? b64tbl[src[2] & 0x3f] : '=';

                ret = prelude_string_ncat(out, &c, 1);
                if ( ret < 0 )
                        return ret;

                src += 3;
        }

        return 0;
}




/**
 * idmef_data_to_string:
 * @data: Pointer to an #idmef_data_t object.
 * @out: Pointer to a #prelude_string_t to store the formated data into.
 *
 * Formats data contained within @data to be printable,
 * and stores the result in the provided @out buffer.
 *
 * Returns: 0 on success, a negative value if an error occured.
 */
int idmef_data_to_string(const idmef_data_t *data, prelude_string_t *out)
{
        int ret = 0;

        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(out, prelude_error(PRELUDE_ERROR_ASSERTION));

        switch ( data->type ) {
        case IDMEF_DATA_TYPE_UNKNOWN:
                return 0;

        case IDMEF_DATA_TYPE_CHAR:
                ret = prelude_string_sprintf(out, "%c", data->data.char_data);
                break;

        case IDMEF_DATA_TYPE_BYTE:
                /*
                 * %hh convertion specifier is not portable.
                 */
                ret = prelude_string_sprintf(out, "%u", (unsigned int) data->data.byte_data);
                break;

        case IDMEF_DATA_TYPE_UINT32:
        case IDMEF_DATA_TYPE_INT:
                ret = prelude_string_sprintf(out, "%" PRELUDE_PRId64, data->data.int_data);
                break;

        case IDMEF_DATA_TYPE_FLOAT:
                ret = prelude_string_sprintf(out, "%f", data->data.float_data);
                break;

        case IDMEF_DATA_TYPE_CHAR_STRING:
                if ( data->len )
                        ret = prelude_string_ncat(out, (const char *) data->data.ro_data, data->len - 1);
                break;

        case IDMEF_DATA_TYPE_BYTE_STRING:
                ret = bytes_to_string(out, data->data.ro_data, data->len);
                break;

        case IDMEF_DATA_TYPE_TIME:
                ret = idmef_time_to_string(data->data.ro_data, out);
                break;
        }

        return ret;
}



/*
 *  This function cannot be declared static because it is invoked
 *  from idmef-tree-wrap.c
 */
void idmef_data_destroy_internal(idmef_data_t *ptr)
{
        prelude_return_if_fail(ptr);

        if ( ptr->type == IDMEF_DATA_TYPE_TIME && ptr->flags & IDMEF_DATA_OWN_DATA )
                idmef_time_destroy(ptr->data.rw_data);

        else if ( (ptr->type == IDMEF_DATA_TYPE_CHAR_STRING || ptr->type == IDMEF_DATA_TYPE_BYTE_STRING) &&
             ptr->flags & IDMEF_DATA_OWN_DATA ) {
                free(ptr->data.rw_data);
                ptr->data.rw_data = NULL;
        }

        /*
         * free() should be done by the caller
         */
}




/**
 * idmef_data_destroy:
 * @data: Pointer to an #idmef_data_t object.
 *
 * Frees @data. The buffer pointed by @data will be freed if
 * the @data object is marked as _dup or _nodup.
 */
void idmef_data_destroy(idmef_data_t *data)
{
        prelude_return_if_fail(data);

        if ( --data->refcount )
                return;

        idmef_data_destroy_internal(data);

        if ( data->flags & IDMEF_DATA_OWN_STRUCTURE )
                free(data);
}


int idmef_data_new_char_string_ref_fast(idmef_data_t **data, const char *ptr, size_t len)
{
        return idmef_data_new_ptr_ref_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}

int idmef_data_new_char_string_dup_fast(idmef_data_t **data, const char *ptr, size_t len)
{
        return idmef_data_new_ptr_dup_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}

int idmef_data_new_char_string_nodup_fast(idmef_data_t **data, char *ptr, size_t len)
{
        return idmef_data_new_ptr_nodup_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}

int idmef_data_set_char_string_ref_fast(idmef_data_t *data, const char *ptr, size_t len)
{
        return idmef_data_set_ptr_ref_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}

int idmef_data_set_char_string_dup_fast(idmef_data_t *data, const char *ptr, size_t len)
{
        return idmef_data_set_ptr_dup_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}

int idmef_data_set_char_string_nodup_fast(idmef_data_t *data, char *ptr, size_t len)
{
        return idmef_data_set_ptr_nodup_fast(data, IDMEF_DATA_TYPE_CHAR_STRING, ptr, len + 1);
}


int idmef_data_new_char_string_ref(idmef_data_t **data, const char *ptr)
{
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));
        return idmef_data_new_char_string_ref_fast(data, ptr, strlen(ptr));
}

int idmef_data_new_char_string_dup(idmef_data_t **data, const char *ptr)
{
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));
        return idmef_data_new_char_string_dup_fast(data, ptr, strlen(ptr));
}

int idmef_data_new_char_string_nodup(idmef_data_t **data, char *ptr)
{
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));
        return idmef_data_new_char_string_nodup_fast(data, ptr, strlen(ptr));
}

int idmef_data_set_char_string_ref(idmef_data_t *data, const char *ptr)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        return idmef_data_set_char_string_ref_fast(data, ptr, strlen(ptr));
}

int idmef_data_set_char_string_dup(idmef_data_t *data, const char *ptr)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        return idmef_data_set_char_string_dup_fast(data, ptr, strlen(ptr));
}

int idmef_data_set_char_string_nodup(idmef_data_t *data, char *ptr)
{
        prelude_return_val_if_fail(data, prelude_error(PRELUDE_ERROR_ASSERTION));
        prelude_return_val_if_fail(ptr, prelude_error(PRELUDE_ERROR_ASSERTION));

        return idmef_data_set_char_string_nodup_fast(data, ptr, strlen(ptr));
}


/*
 *
 */
int idmef_data_new_byte_string_ref(idmef_data_t **data, const unsigned char *ptr, size_t len)
{
        return idmef_data_new_ptr_ref_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}


int idmef_data_new_byte_string_dup(idmef_data_t **data, const unsigned char *ptr, size_t len)
{
        return idmef_data_new_ptr_dup_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}


int idmef_data_new_byte_string_nodup(idmef_data_t **data, unsigned char *ptr, size_t len)
{
        return idmef_data_new_ptr_nodup_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}


int idmef_data_set_byte_string_ref(idmef_data_t *data, const unsigned char *ptr, size_t len)
{
        return idmef_data_set_ptr_ref_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}


int idmef_data_set_byte_string_dup(idmef_data_t *data, const unsigned char *ptr, size_t len)
{
        return idmef_data_set_ptr_dup_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}


int idmef_data_set_byte_string_nodup(idmef_data_t *data, unsigned char *ptr, size_t len)
{
        return idmef_data_set_ptr_nodup_fast(data, IDMEF_DATA_TYPE_BYTE_STRING, ptr, len);
}



int idmef_data_compare(const idmef_data_t *data1, const idmef_data_t *data2)
{
        if ( ! data1 && ! data2 )
                return 0;

        else if ( ! data1 || ! data2 )
                return (data1) ? 1 : -1;

        else if ( data1->len != data2->len )
                return (data1->len > data2->len) ? 1 : -1;

        else if ( data1->type != data2->type )
                return -1;

        if ( data1->type == IDMEF_DATA_TYPE_TIME )
                return idmef_time_compare(data1->data.ro_data, data2->data.ro_data);

        else if ( data1->type == IDMEF_DATA_TYPE_CHAR_STRING || data1->type == IDMEF_DATA_TYPE_BYTE_STRING )
                return memcmp(data1->data.ro_data, data2->data.ro_data, data1->len);

        else
                return memcmp(&data1->data.char_data, &data2->data.char_data, data1->len);
}
