/* Copyright (C) 2007-2016 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.
 */

#include "suricata-common.h"
#include "detect-engine-prefilter.h"
#include "detect-engine-prefilter-common.h"

typedef struct PrefilterPacketHeaderHashCtx_ {
    PrefilterPacketHeaderValue v1;

    uint16_t type;  /**< PREFILTER_EXTRA_MATCH_* */
    uint16_t value;

    uint32_t cnt;
} PrefilterPacketHeaderHashCtx;

static uint32_t PrefilterPacketHeaderHashFunc(HashListTable *ht, void *data, uint16_t datalen)
{
    PrefilterPacketHeaderCtx *ctx = data;
    uint64_t hash = ctx->v1.u64[0] + ctx->v1.u64[1] + ctx->type + ctx->value;
    hash %= ht->array_size;
    return hash;
}

static char PrefilterPacketHeaderCompareFunc(void *data1, uint16_t len1,
                                       void *data2, uint16_t len2)
{
    PrefilterPacketHeaderHashCtx *ctx1 = data1;
    PrefilterPacketHeaderHashCtx *ctx2 = data2;
    return (ctx1->v1.u64[0] == ctx2->v1.u64[0] &&
            ctx1->v1.u64[1] == ctx2->v1.u64[1] &&
            ctx1->type == ctx2->type &&
            ctx1->value == ctx2->value);
}

static void PrefilterPacketHeaderFreeFunc(void *ptr)
{
    SCFree(ptr);
}

static void PrefilterPacketHeaderFree(void *pectx)
{
    PrefilterPacketHeaderCtx *ctx = pectx;
    SCFree(ctx->sigs_array);
    SCFree(ctx);
}

static void PrefilterPacketU8HashCtxFree(void *vctx)
{
    PrefilterPacketU8HashCtx *ctx = vctx;
    int i;
    for (i = 0; i < 256; i++) {
        SigsArray *sa = ctx->array[i];
        if (sa == NULL)
            continue;
        SCFree(sa->sigs);
        SCFree(sa);
    }
    SCFree(ctx);
}

static void GetExtraMatch(const Signature *s, uint16_t *type, uint16_t *value)
{
    if (s->sp != NULL && s->sp->next == NULL && s->sp->port == s->sp->port2 &&
        !(s->sp->flags & PORT_FLAG_NOT))
    {
        *type = PREFILTER_EXTRA_MATCH_SRCPORT;
        *value = s->sp->port;
    } else if (s->alproto != ALPROTO_UNKNOWN) {
        *type = PREFILTER_EXTRA_MATCH_ALPROTO;
        *value = s->alproto;
    } else if (s->dp != NULL && s->dp->next == NULL && s->dp->port == s->dp->port2 &&
        !(s->dp->flags & PORT_FLAG_NOT))
    {
        *type = PREFILTER_EXTRA_MATCH_DSTPORT;
        *value = s->dp->port;
    }
}

/** \internal
 */
static int
SetupEngineForPacketHeader(DetectEngineCtx *de_ctx, SigGroupHead *sgh,
        int sm_type, PrefilterPacketHeaderHashCtx *hctx,
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx))
{
    Signature *s = NULL;
    uint32_t sig = 0;
    uint32_t sig_offset = 0;

    PrefilterPacketHeaderCtx *ctx = SCCalloc(1, sizeof(PrefilterPacketHeaderCtx));
    if (ctx == NULL)
        return -1;

    ctx->v1 = hctx->v1;
    ctx->type = hctx->type;
    ctx->value = hctx->value;

    ctx->sigs_cnt = hctx->cnt;
    ctx->sigs_array = SCCalloc(ctx->sigs_cnt, sizeof(SigIntId));
    if (ctx->sigs_array == NULL) {
        SCFree(ctx);
        return -1;
    }

    for (sig = 0; sig < sgh->init->sig_cnt; sig++) {
        s = sgh->init->match_array[sig];
        if (s == NULL)
            continue;
        if (s->init_data->prefilter_sm == NULL || s->init_data->prefilter_sm->type != sm_type)
            continue;

        uint16_t type = 0;
        uint16_t value = 0;
        GetExtraMatch(s, &type, &value);

        if (Compare(ctx->v1, s->init_data->prefilter_sm->ctx) &&
            ctx->type == type && ctx->value == value)
        {
            SCLogDebug("appending sid %u on %u", s->id, sig_offset);
            ctx->sigs_array[sig_offset] = s->num;
            sig_offset++;

            s->flags |= SIG_FLAG_PREFILTER;
        }
    }

    SCLogDebug("%s: ctx %p extra type %u extra value %u, sig cnt %u",
            sigmatch_table[sm_type].name, ctx, ctx->type, ctx->value,
            ctx->sigs_cnt);
    PrefilterAppendEngine(de_ctx, sgh, Match, ctx,
            PrefilterPacketHeaderFree, sigmatch_table[sm_type].name);
    return 0;
}

/** \internal
 *  \brief apply signature to each value */
static void ApplyToU8Hash(PrefilterPacketU8HashCtx *ctx, PrefilterPacketHeaderValue v, Signature *s)
{
    switch (v.u8[0]) {
        case PREFILTER_U8HASH_MODE_EQ:
            {
                SigsArray *sa = ctx->array[v.u8[1]];
                sa->sigs[sa->offset++] = s->num;
                break;
            }
        case PREFILTER_U8HASH_MODE_LT:
            {
                uint8_t x = v.u8[1] - 1;
                do {
                    SigsArray *sa = ctx->array[x];
                    sa->sigs[sa->offset++] = s->num;
                } while (x--);

                break;
            }
        case PREFILTER_U8HASH_MODE_GT:
            {
                int x = v.u8[1] + 1;
                do {
                    SigsArray *sa = ctx->array[x];
                    sa->sigs[sa->offset++] = s->num;
                } while (++x < 256);

                break;
            }
        case PREFILTER_U8HASH_MODE_RA:
            {
                int x = v.u8[1] + 1;
                do {
                    SigsArray *sa = ctx->array[x];
                    sa->sigs[sa->offset++] = s->num;
                } while (++x < v.u8[2]);

                break;
            }
    }
}

/** \internal
 *  \brief turn values into a u8 hash map
 *  \todo improve error handling
 *  \todo deduplicate sigs arrays
 */
static int
SetupEngineForPacketHeaderPrefilterPacketU8HashCtx(DetectEngineCtx *de_ctx,
        SigGroupHead *sgh, int sm_type, uint32_t *counts,
        void (*Set)(PrefilterPacketHeaderValue *v, void *),
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx))
{
    Signature *s = NULL;
    uint32_t sig = 0;
    uint32_t cnt = 0;

    PrefilterPacketU8HashCtx *ctx = SCCalloc(1, sizeof(PrefilterPacketU8HashCtx));
    if (ctx == NULL)
        return -1;

    int set_cnt = 0;
    for (int i = 0; i < 256; i++) {
        if (counts[i] == 0)
            continue;
        ctx->array[i] = SCCalloc(1, sizeof(SigsArray));
        BUG_ON(ctx->array[i] == NULL);

        ctx->array[i]->cnt = counts[i];
        ctx->array[i]->sigs = SCCalloc(ctx->array[i]->cnt, sizeof(SigIntId));
        BUG_ON(ctx->array[i]->sigs == NULL);
        set_cnt++;
    }
    if (set_cnt == 0) {
        /* not an error */
        PrefilterPacketU8HashCtxFree(ctx);
        return 0;
    }

    for (sig = 0; sig < sgh->init->sig_cnt; sig++) {
        s = sgh->init->match_array[sig];
        if (s == NULL)
            continue;
        if (s->init_data->prefilter_sm == NULL || s->init_data->prefilter_sm->type != sm_type)
            continue;

        PrefilterPacketHeaderValue v;
        memset(&v, 0, sizeof(v));
        Set(&v, s->init_data->prefilter_sm->ctx);

        ApplyToU8Hash(ctx, v, s);
        s->flags |= SIG_FLAG_PREFILTER;
        cnt++;
    }

    if (cnt) {
        PrefilterAppendEngine(de_ctx, sgh, Match, ctx,
                PrefilterPacketU8HashCtxFree,
                sigmatch_table[sm_type].name);
    } else {
        PrefilterPacketU8HashCtxFree(ctx);
    }
    return 0;
}

/** \internal
 *  \brief setup a engine for each unique value
 */
static void SetupSingle(DetectEngineCtx *de_ctx, HashListTable *hash_table,
        SigGroupHead *sgh, int sm_type,
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx,
            Packet *p, const void *pectx))
{
    HashListTableBucket *hb = HashListTableGetListHead(hash_table);
    for ( ; hb != NULL; hb = HashListTableGetListNext(hb)) {
        PrefilterPacketHeaderHashCtx *ctx = HashListTableGetListData(hb);

        SetupEngineForPacketHeader(de_ctx, sgh, sm_type,
                ctx, Compare, Match);
    }
}

/** \internal
 *  \brief setup a single engine with a hash map for u8 values
 */
static void SetupU8Hash(DetectEngineCtx *de_ctx, HashListTable *hash_table,
        SigGroupHead *sgh, int sm_type,
        void (*Set)(PrefilterPacketHeaderValue *v, void *),
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx,
            Packet *p, const void *pectx))
{
    uint32_t counts[256];
    memset(&counts, 0, sizeof(counts));

    HashListTableBucket *hb = HashListTableGetListHead(hash_table);
    for ( ; hb != NULL; hb = HashListTableGetListNext(hb)) {
        PrefilterPacketHeaderHashCtx *ctx = HashListTableGetListData(hb);

        switch (ctx->v1.u8[0]) {
            case PREFILTER_U8HASH_MODE_EQ:
                counts[ctx->v1.u8[1]] += ctx->cnt;
                break;
            case PREFILTER_U8HASH_MODE_LT:
            {
                uint8_t v = ctx->v1.u8[1];
                while (v > 0) {
                    v--;
                    counts[v] += ctx->cnt;
                }

                break;
            }
            case PREFILTER_U8HASH_MODE_GT:
            {
                uint8_t v = ctx->v1.u8[1];
                while (v < UINT8_MAX) {
                    v++;
                    counts[v] += ctx->cnt;
                }

                break;
            }
            case PREFILTER_U8HASH_MODE_RA:
            {
                if (ctx->v1.u8[1] < ctx->v1.u8[2]) {
                    // ctx->v1.u8[1] is not UINT8_MAX
                    uint8_t v = ctx->v1.u8[1] + 1;
                    while (v < ctx->v1.u8[2]) {
                        counts[v] += ctx->cnt;
                        v++;
                    }
                }
                break;
            }
        }
    }

    SetupEngineForPacketHeaderPrefilterPacketU8HashCtx(de_ctx, sgh, sm_type,
            counts, Set, Compare, Match);
}

static int PrefilterSetupPacketHeaderCommon(DetectEngineCtx *de_ctx,
        SigGroupHead *sgh, int sm_type,
        void (*Set)(PrefilterPacketHeaderValue *v, void *),
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx,
                      Packet *p, const void *pectx),
        bool u8hash)
{
    Signature *s = NULL;
    uint32_t sig = 0;

    if (sgh == NULL)
        return 0;

    /* first count how many engines we will need */

    HashListTable *hash_table = HashListTableInit(4096,
            PrefilterPacketHeaderHashFunc,
            PrefilterPacketHeaderCompareFunc,
            PrefilterPacketHeaderFreeFunc);
    if (hash_table == NULL)
        return -1;

    for (sig = 0; sig < sgh->init->sig_cnt; sig++) {
        s = sgh->init->match_array[sig];
        if (s == NULL)
            continue;
        if (s->init_data->prefilter_sm == NULL || s->init_data->prefilter_sm->type != sm_type)
            continue;

        PrefilterPacketHeaderHashCtx ctx;
        memset(&ctx, 0, sizeof(ctx));
        Set(&ctx.v1, s->init_data->prefilter_sm->ctx);

        GetExtraMatch(s, &ctx.type, &ctx.value);

        PrefilterPacketHeaderHashCtx *rctx = HashListTableLookup(hash_table, (void *)&ctx, 0);
        if (rctx != 0) {
            rctx->cnt++;
        } else {
            PrefilterPacketHeaderHashCtx *actx = SCCalloc(1, sizeof(*actx));
            if (actx == NULL)
                goto error;

            Set(&actx->v1, s->init_data->prefilter_sm->ctx);
            actx->cnt = 1;
            actx->type = ctx.type;
            actx->value = ctx.value;

            int ret = HashListTableAdd(hash_table, actx, 0);
            if (ret != 0) {
                SCFree(actx);
                goto error;
            }
        }
    }

    if (!u8hash) {
        SetupSingle(de_ctx, hash_table, sgh, sm_type, Compare, Match);
    } else {
        SetupU8Hash(de_ctx, hash_table, sgh, sm_type, Set, Compare, Match);
    }

    HashListTableFree(hash_table);
    return 0;
error:
    HashListTableFree(hash_table);
    return -1;
}

int PrefilterSetupPacketHeaderU8Hash(DetectEngineCtx *de_ctx,
        SigGroupHead *sgh, int sm_type,
        void (*Set)(PrefilterPacketHeaderValue *v, void *),
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx,
                      Packet *p, const void *pectx))
{
    return PrefilterSetupPacketHeaderCommon(de_ctx, sgh, sm_type, Set, Compare, Match, true);
}

int PrefilterSetupPacketHeader(DetectEngineCtx *de_ctx,
        SigGroupHead *sgh, int sm_type,
        void (*Set)(PrefilterPacketHeaderValue *v, void *),
        bool (*Compare)(PrefilterPacketHeaderValue v, void *),
        void (*Match)(DetectEngineThreadCtx *det_ctx,
        Packet *p, const void *pectx))
{
    return PrefilterSetupPacketHeaderCommon(de_ctx, sgh, sm_type, Set, Compare, Match, false);
}
