#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/if_tun.h>

#ifdef SUPPORT_BPF
#include "bpf_config_user.h"

#define MIN(a, b) ((a) > (b) ? (b) : (a))

struct bpf_obj_ctx
{
    char obj_file[1024];
    struct bpf_config config;

    int prog_fd;
    struct bpf_object *prog_obj;
};

void bpf_obj_unload(struct bpf_obj_ctx *ctx)
{
    if (ctx)
    {
        if (ctx->prog_fd > 0)
        {
            close(ctx->prog_fd);
        }

        if (ctx->prog_obj)
        {
            bpf_object__close(ctx->prog_obj);
            ctx->prog_obj = NULL;
        }

        free(ctx);
        ctx = NULL;
    }
}

/*
 * queue_num : same as worker thread number
 * hash_mode : 2: hash with src/dst addr
 *             4: hash with src/dst addr and src/dst port
 * debug_log : 0 for disable(only use for debug, printf bpf debug log(cat /sys/kernel/debug/tracing/trace_pipe)
 */
struct bpf_obj_ctx *bpf_obj_load(const char *obj_file, uint32_t queue_num, uint32_t hash_mode, uint32_t debug_log)
{
    struct bpf_obj_ctx *ctx = (struct bpf_obj_ctx *)calloc(1, sizeof(struct bpf_obj_ctx));

    strncpy(ctx->obj_file, obj_file, MIN(strlen(obj_file), sizeof(ctx->obj_file)));
    bpf_config_set_queue_num(&ctx->config, queue_num);
    bpf_config_set_hash_mode(&ctx->config, hash_mode);
    bpf_config_set_debug_log(&ctx->config, debug_log);

    if (bpf_prog_load(ctx->obj_file, BPF_PROG_TYPE_SOCKET_FILTER, &ctx->prog_obj, &ctx->prog_fd) < 0)
    {
        printf("ERROR: Unable to load bpf object %s, %s.\n", ctx->obj_file, strerror(errno));
        goto error;
    }

    if (bpf_config_update_map(&ctx->config, ctx->prog_obj) == -1)
    {
        goto error;
    }

    return ctx;

error:
    bpf_obj_unload(ctx);

    return NULL;
}

/*
 * return  0 : success
 * return -1 : error
 */
int bpf_obj_attach(struct bpf_obj_ctx *ctx, int fd)
{
    if (ctx && ctx->prog_fd > 0)
    {
        if (ioctl(fd, TUNSETSTEERINGEBPF, (void *)&ctx->prog_fd) == -1)
        {
            printf("ERROR: Unable to set bpf on sockfd %d, %s.\n", fd, strerror(errno));
            return -1;
        }
        else
        {
            printf("Set TUNSETSTEERINGEBPF on fd: %d\n", fd);
            return 0;
        }
    }
    else
    {
        return -1;
    }
}
#else

struct bpf_obj_ctx
{
};

#include <sys/types.h>

void bpf_obj_unload(struct bpf_obj_ctx *ctx)
{
    printf("ERROR: BPF feature not support on current system\n");
}

struct bpf_obj_ctx *bpf_obj_load(const char *obj_file, uint32_t queue_num, uint32_t hash_mode, uint32_t debug_log)
{
    printf("ERROR: BPF feature not support on current system\n");
    return NULL;
}

int bpf_obj_attach(struct bpf_obj_ctx *ctx, int fd)
{
    printf("ERROR: BPF feature not support on current system\n");
    return -1;
}
#endif