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

/*
 * Isolated reproducer for Suricata tag suricata-8.0.7.
 * The option table, parser, matcher and option flags below are copied from
 * src/detect-ipopts.c, src/decode-ipv4.h and src/suricata-common.h.
 * Other engine types/macros are minimal stubs. This does not run Suricata's
 * packet decoder or complete detection pipeline.
 * Build: cc -std=c11 -O2 suricata-ipopts-any-reproducer.c -o ipopts-repro
 * Run: ./ipopts-repro
 * Source: https://github.com/OISF/suricata/tree/suricata-8.0.7
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <assert.h>
typedef void DetectEngineThreadCtx;
typedef void Signature;
typedef void SigMatchCtx;
typedef struct { uint16_t ipopt; } DetectIpOptsData;
typedef struct { int ipv4; struct { struct { struct { uint16_t opts_set; } ip4; } vars; } l3; } Packet;
#define DEBUG_VALIDATE_BUG_ON(x) assert(!(x))
#define PKT_IS_PSEUDOPKT(p) 0
#define PacketIsIPv4(p) ((p)->ipv4)
#define SCMalloc malloc
#define unlikely(x) (x)
#define SCLogError(...) ((void)0)

#define BIT_U16(n) ((uint16_t)(1 << (n)))
#define IPV4_OPT_FLAG_EOL    BIT_U16(1)
#define IPV4_OPT_FLAG_NOP    BIT_U16(2)
#define IPV4_OPT_FLAG_RR     BIT_U16(3)
#define IPV4_OPT_FLAG_TS     BIT_U16(4)
#define IPV4_OPT_FLAG_QS     BIT_U16(5)
#define IPV4_OPT_FLAG_LSRR   BIT_U16(6)
#define IPV4_OPT_FLAG_SSRR   BIT_U16(7)
#define IPV4_OPT_FLAG_SID    BIT_U16(8)
#define IPV4_OPT_FLAG_SEC    BIT_U16(9)
#define IPV4_OPT_FLAG_CIPSO  BIT_U16(10)
#define IPV4_OPT_FLAG_RTRALT BIT_U16(11)
#define IPV4_OPT_FLAG_ESEC   BIT_U16(12)
struct DetectIpOpts_ {
    const char *ipopt_name;   /**< ip option name */
    uint16_t code;   /**< ip option flag value */
} ipopts[] = {
    {
            "ts",
            IPV4_OPT_FLAG_TS,
    },
    {
            "rr",
            IPV4_OPT_FLAG_RR,
    },
    {
            "qs",
            IPV4_OPT_FLAG_QS,
    },
    {
            "sec",
            IPV4_OPT_FLAG_SEC,
    },
    {
            "lsrr",
            IPV4_OPT_FLAG_LSRR,
    },
    {
            "esec",
            IPV4_OPT_FLAG_ESEC,
    },
    {
            "cipso",
            IPV4_OPT_FLAG_CIPSO,
    },
    {
            "satid",
            IPV4_OPT_FLAG_SID,
    },
    {
            "ssrr",
            IPV4_OPT_FLAG_SSRR,
    },
    {
            "rtralt",
            IPV4_OPT_FLAG_RTRALT,
    },
    {
            "eol",
            IPV4_OPT_FLAG_EOL,
    },
    {
            "nop",
            IPV4_OPT_FLAG_NOP,
    },
    {
            "any",
            0xffff,
    },
    { NULL, 0 },
};
static int DetectIpOptsMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
        const Signature *s, const SigMatchCtx *ctx)
{
    DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p));

    const DetectIpOptsData *de = (const DetectIpOptsData *)ctx;

    if (!de || !PacketIsIPv4(p))
        return 0;

    return (p->l3.vars.ip4.opts_set & de->ipopt) == de->ipopt;
}
static DetectIpOptsData *DetectIpOptsParse (const char *rawstr)
{
    if (rawstr == NULL || strlen(rawstr) == 0)
        return NULL;

    int i;
    bool found = false;
    for(i = 0; ipopts[i].ipopt_name != NULL; i++)  {
        if((strcasecmp(ipopts[i].ipopt_name,rawstr)) == 0) {
            found = true;
            break;
        }
    }

    if (!found) {
        SCLogError("unknown IP option specified \"%s\"", rawstr);
        return NULL;
    }

    DetectIpOptsData *de = SCMalloc(sizeof(DetectIpOptsData));
    if (unlikely(de == NULL))
        return NULL;

    de->ipopt = ipopts[i].code;

    return de;
}

int main(void) {
    const char *names[] = { "any", "rr", "ts", "nop", "eol" };
    const uint16_t masks[] = { 0, IPV4_OPT_FLAG_RR, IPV4_OPT_FLAG_TS,
                              IPV4_OPT_FLAG_NOP, IPV4_OPT_FLAG_EOL,
                              IPV4_OPT_FLAG_RR | IPV4_OPT_FLAG_TS, 0x1ffe };
    puts("opts_set  any rr ts nop eol");
    for (unsigned i = 0; i < sizeof(masks)/sizeof(masks[0]); i++) {
        Packet p = { .ipv4 = 1 };
        p.l3.vars.ip4.opts_set = masks[i];
        printf("0x%04x  ", masks[i]);
        for (unsigned j = 0; j < sizeof(names)/sizeof(names[0]); j++) {
            DetectIpOptsData *de = DetectIpOptsParse(names[j]);
            assert(de);
            printf("%d  ", DetectIpOptsMatch(NULL, &p, NULL, de));
            free(de);
        }
        puts("");
    }
    uint16_t possible = 0;
    for (unsigned i = 0; ipopts[i].ipopt_name; i++)
        if (strcmp(ipopts[i].ipopt_name, "any")) possible |= ipopts[i].code;
    unsigned checked = 0, matches = 0;
    DetectIpOptsData *de = DetectIpOptsParse("any");
    for (unsigned mask = 0; mask <= UINT16_MAX; mask++) {
        if (mask & (uint16_t)~possible) continue;
        Packet p = { .ipv4 = 1 };
        p.l3.vars.ip4.opts_set = mask;
        checked++;
        matches += DetectIpOptsMatch(NULL, &p, NULL, de);
    }
    printf("possible_flags=0x%04x, tested_subsets=%u, any_matches=%u\n", possible, checked, matches);
    assert(possible == 0x1ffe && checked == 4096 && matches == 0);
    Packet ipv6 = { .ipv4 = 0 };
    assert(DetectIpOptsMatch(NULL, &ipv6, NULL, de) == 0);
    free(de);
}
