/* Copyright (C) 2017-2020 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * 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
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 *
 * \author Victor Julien <victor@inliniac.net>
 *
 * Implements the bsize generic buffer length keyword
 */

#include "suricata-common.h"
#include "util-unittest.h"
#include "util-unittest-helper.h"

#include "detect.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "detect-content.h"

#include "detect-bsize.h"

#include "util-misc.h"

/*prototypes*/
static int DetectBsizeSetup (DetectEngineCtx *, Signature *, const char *);
static void DetectBsizeFree (DetectEngineCtx *, void *);
#ifdef UNITTESTS
static void DetectBsizeRegisterTests (void);
#endif

/**
 * \brief Registration function for bsize: keyword
 */

void DetectBsizeRegister(void)
{
    sigmatch_table[DETECT_BSIZE].name = "bsize";
    sigmatch_table[DETECT_BSIZE].desc = "match on the length of a buffer";
    sigmatch_table[DETECT_BSIZE].url = "/rules/payload-keywords.html#bsize";
    sigmatch_table[DETECT_BSIZE].Match = NULL;
    sigmatch_table[DETECT_BSIZE].Setup = DetectBsizeSetup;
    sigmatch_table[DETECT_BSIZE].Free = DetectBsizeFree;
#ifdef UNITTESTS
    sigmatch_table[DETECT_BSIZE].RegisterTests = DetectBsizeRegisterTests;
#endif
}

#define DETECT_BSIZE_LT 0
#define DETECT_BSIZE_GT 1
#define DETECT_BSIZE_RA 2
#define DETECT_BSIZE_EQ 3

typedef struct DetectBsizeData {
    uint8_t mode;
    uint64_t lo;
    uint64_t hi;
} DetectBsizeData;

/** \brief bsize match function
 *
 *  \param ctx match ctx
 *  \param buffer_size size of the buffer
 *  \param eof is the buffer closed?
 *
 *  \retval r 1 match, 0 no match, -1 can't match
 *
 *  \todo check logic around < vs <=
 */
int DetectBsizeMatch(const SigMatchCtx *ctx, const uint64_t buffer_size, bool eof)
{
    const DetectBsizeData *bsz = (const DetectBsizeData *)ctx;
    switch (bsz->mode) {
        case DETECT_BSIZE_LT:
            if (buffer_size < bsz->lo) {
                return 1;
            }
            return -1;

        case DETECT_BSIZE_GT:
            if (buffer_size > bsz->lo) {
                return 1;
            } else if (eof) {
                return -1;
            }
            return 0;

        case DETECT_BSIZE_EQ:
            if (buffer_size == bsz->lo) {
                return 1;
            } else if (buffer_size > bsz->lo) {
                return -1;
            } else if (eof) {
                return -1;
            } else {
                return 0;
            }

        case DETECT_BSIZE_RA:
            if (buffer_size > bsz->lo && buffer_size < bsz->hi) {
                return 1;
            } else if (buffer_size <= bsz->lo && eof) {
                return -1;
            } else if (buffer_size <= bsz->lo) {
                return 0;
            } else if (buffer_size >= bsz->hi) {
                return -1;
            }
    }
    return 0;
}

#define ERR(...) do { \
    char _buf[2048];              \
    snprintf(_buf, sizeof(_buf), __VA_ARGS__);  \
    SCLogError(SC_ERR_INVALID_RULE_ARGUMENT, "bsize: bad input, %s", _buf); \
} while(0)

/**
 * \brief This function is used to parse bsize options passed via bsize: keyword
 *
 * \param bsizestr Pointer to the user provided bsize options
 *
 * \retval bsized pointer to DetectBsizeData on success
 * \retval NULL on failure
 */

static DetectBsizeData *DetectBsizeParse (const char *str)
{
    uint32_t lo = 0;
    uint32_t hi = 0;

    if (str == NULL)
        return NULL;

    size_t len = strlen(str);
    if (len == 0)
        return NULL;

    /* allow for leading spaces */
    while (isspace(*str))
        (str++);
    len = strlen(str);
    if (len == 0)
        return NULL;

    int mode = DETECT_BSIZE_EQ;
    switch (*str) {
        case '>':
            mode = DETECT_BSIZE_GT;
            str++;
            break;
        case '<':
            mode = DETECT_BSIZE_LT;
            str++;
            break;
    }

    /* allow for spaces between mode and value */
    while (isspace(*str))
        (str++);

    char str1[11], *p = str1;
    memset(str1, 0, sizeof(str1));
    while (*str && isdigit(*str)) {
        if (p - str1 >= ((int)sizeof(str1) - 1))
            return NULL;
        *p++ = *str++;
    }
    /* skip trailing space */
    while (*str && isspace(*str)) {
        str++;
    }
    if (*str == '\0') {
        // done
        SCLogDebug("str1 '%s'", str1);

        uint64_t val = 0;
        if (ParseSizeStringU64(str1, &val) < 0) {
            return NULL;
        }
        lo = val;

    } else if (*str == '<') {
        str++;
        if (*str != '>') {
            ERR("only '<>' allowed");
            return NULL;
        }
        str++;

        // range
        if (mode != DETECT_BSIZE_EQ) {
            ERR("mode already set");
            return NULL;
        }
        mode = DETECT_BSIZE_RA;

        uint64_t val = 0;
        if (ParseSizeStringU64(str1, &val) < 0) {
            return NULL;
        }
        lo = val;

        /* allow for spaces between mode and value */
        while (*str && isspace(*str))
            (str++);

        char str2[11];
        p = str2;
        memset(str2, 0, sizeof(str2));
        while (*str && isdigit(*str)) {
            if (p - str2 >= ((int)sizeof(str2) - 1))
                return NULL;
            *p++ = *str++;
        }
        /* skip trailing space */
        while (*str && isspace(*str)) {
            str++;
        }
        if (*str == '\0') {
            // done
            SCLogDebug("str2 '%s'", str2);

            if (ParseSizeStringU64(str2, &val) < 0) {
                ERR("'%s' is not a valid u32", str2);
                return NULL;
            }
            hi = val;
            if (lo >= hi) {
                ERR("%u > %u", lo, hi);
                return NULL;
            }

        } else {
            ERR("trailing data");
            return NULL;
        }

    } else {
        ERR("'%s'", str);
        return NULL;
    }

    DetectBsizeData *bsz = SCCalloc(1, sizeof(*bsz));
    if (bsz == NULL) {
        return NULL;
    }
    bsz->mode = (uint8_t)mode;
    bsz->lo = lo;
    bsz->hi = hi;
    return bsz;
}

/**
 * \brief this function is used to parse bsize data into the current signature
 *
 * \param de_ctx pointer to the Detection Engine Context
 * \param s pointer to the Current Signature
 * \param bsizestr pointer to the user provided bsize options
 *
 * \retval 0 on Success
 * \retval -1 on Failure
 */
static int DetectBsizeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *sizestr)
{
    SCEnter();
    SigMatch *sm = NULL;

    if (DetectBufferGetActiveList(de_ctx, s) == -1)
        SCReturnInt(-1);

    int list = s->init_data->list;
    if (list == DETECT_SM_LIST_NOTSET)
        SCReturnInt(-1);

    DetectBsizeData *bsz = DetectBsizeParse(sizestr);
    if (bsz == NULL)
        goto error;
    sm = SigMatchAlloc();
    if (sm == NULL)
        goto error;
    sm->type = DETECT_BSIZE;
    sm->ctx = (void *)bsz;

    SigMatchAppendSMToList(s, sm, list);

    SCReturnInt(0);

error:
    DetectBsizeFree(de_ctx, bsz);
    SCReturnInt(-1);
}

/**
 * \brief this function will free memory associated with DetectBsizeData
 *
 * \param ptr pointer to DetectBsizeData
 */
void DetectBsizeFree(DetectEngineCtx *de_ctx, void *ptr)
{
    if (ptr == NULL)
        return;

    DetectBsizeData *bsz = (DetectBsizeData *)ptr;
    SCFree(bsz);
}

#ifdef UNITTESTS
#include "tests/detect-bsize.c"
#endif
