#include <string.h>

#include "dablooms.h"
#include "tcp_utils.h"
#include "udp_utils.h"
#include "ipv4_utils.h"
#include "duplicated_packet_filter.h"

struct duplicated_packet_key
{
    // TCP or UDP
    uint32_t seq;
    uint32_t ack;
    uint16_t src_port; /* host order */
    uint16_t dst_port; /* host order */
    uint16_t l4_checksum;

    // IPv4
    uint16_t ip_id;
    uint32_t src_addr; /* host order */
    uint32_t dst_addr; /* host order */
} __attribute__((__packed__));

struct duplicated_packet_filter
{
    struct expiry_dablooms_handle *dablooms;
};

/******************************************************************************
 * Private API
 ******************************************************************************/

// return  0: success
// reutrn -1: error
static inline int duplicated_packet_key_get(const struct packet *packet, struct duplicated_packet_key *key)
{
    const struct packet_layer *ipv4_layer = packet_get_innermost_layer(packet, LAYER_TYPE_IPV4);
    if (ipv4_layer == NULL)
    {
        return -1;
    }
    const struct packet_layer *l4_layer = packet_get_innermost_layer(packet, (enum layer_type)(LAYER_TYPE_TCP | LAYER_TYPE_UDP));
    if (l4_layer == NULL)
    {
        return -1;
    }

    memset(key, 0, sizeof(struct duplicated_packet_key));

    const struct ip *iphdr = (const struct ip *)ipv4_layer->hdr_ptr;
    key->ip_id = ipv4_hdr_get_ipid(iphdr);
    key->src_addr = ipv4_hdr_get_src_addr(iphdr);
    key->dst_addr = ipv4_hdr_get_dst_addr(iphdr);

    if (l4_layer->type == LAYER_TYPE_TCP)
    {
        const struct tcphdr *tcphdr = (const struct tcphdr *)l4_layer->hdr_ptr;
        key->seq = tcp_hdr_get_seq(tcphdr);
        key->ack = tcp_hdr_get_ack(tcphdr);
        key->src_port = tcp_hdr_get_src_port(tcphdr);
        key->dst_port = tcp_hdr_get_dst_port(tcphdr);
        key->l4_checksum = tcp_hdr_get_checksum(tcphdr);
    }
    else
    {
        const struct udphdr *udphdr = (const struct udphdr *)l4_layer->hdr_ptr;
        key->src_port = udp_hdr_get_src_port(udphdr);
        key->dst_port = udp_hdr_get_dst_port(udphdr);
        key->l4_checksum = udp_hdr_get_checksum(udphdr);
    }

    return 0;
}

/******************************************************************************
 * Public API
 ******************************************************************************/

struct duplicated_packet_filter *duplicated_packet_filter_new(uint32_t capacity, uint32_t timeout, double error_rate, uint64_t now)
{
    struct duplicated_packet_filter *filter = (struct duplicated_packet_filter *)calloc(1, sizeof(struct duplicated_packet_filter));
    if (filter == NULL)
    {
        return NULL;
    }

    filter->dablooms = expiry_dablooms_new(capacity, error_rate, now, timeout);
    if (filter->dablooms == NULL)
    {
        free(filter);
        return NULL;
    }

    return filter;
}

void duplicated_packet_filter_free(struct duplicated_packet_filter *filter)
{
    if (filter)
    {
        if (filter->dablooms)
        {
            expiry_dablooms_free(filter->dablooms);
            filter->dablooms = NULL;
        }
        free(filter);
        filter = NULL;
    }
}

// return 1: found
// reutrn 0: no found
int duplicated_packet_filter_lookup(struct duplicated_packet_filter *filter, const struct packet *packet, uint64_t now)
{
    struct duplicated_packet_key key;
    if (duplicated_packet_key_get(packet, &key) == -1)
    {
        return 0;
    }

    if (expiry_dablooms_search(filter->dablooms, (const char *)&key, sizeof(struct duplicated_packet_key), now) == 1)
    {
        return 1;
    }

    return 0;
}

void duplicated_packet_filter_add(struct duplicated_packet_filter *filter, const struct packet *packet, uint64_t now)
{
    struct duplicated_packet_key key;
    if (duplicated_packet_key_get(packet, &key) == -1)
    {
        return;
    }

    expiry_dablooms_add(filter->dablooms, (const char *)&key, sizeof(struct duplicated_packet_key), now);
}
