/*
 * Copyright (c) 2018-2026 OARC, Inc.
 * All rights reserved.
 *
 * This file is part of dnsjit.
 *
 * dnsjit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * dnsjit 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
 * along with dnsjit.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "filter/layer.h"
#include "core/assert.h"

#include <string.h>
#include <pcap/pcap.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#ifdef HAVE_NET_ETHERNET_H
#include <net/ethernet.h>
#endif
#ifdef HAVE_NET_ETHERTYPES_H
#include <net/ethertypes.h>
#endif
#ifdef HAVE_ENDIAN_H
#include <endian.h>
#else
#ifdef HAVE_SYS_ENDIAN_H
#include <sys/endian.h>
#else
#ifdef HAVE_MACHINE_ENDIAN_H
#include <machine/endian.h>
#endif
#endif
#endif
#ifdef HAVE_BYTESWAP_H
#include <byteswap.h>
#endif
#ifndef bswap_16
#ifndef bswap16
#define bswap_16(x) swap16(x)
#define bswap_32(x) swap32(x)
#define bswap_64(x) swap64(x)
#else
#define bswap_16(x) bswap16(x)
#define bswap_32(x) bswap32(x)
#define bswap_64(x) bswap64(x)
#endif
#endif

#define N_IEEE802 3

static core_log_t     _log      = LOG_T_INIT("filter.layer");
static filter_layer_t _defaults = {
    LOG_T_INIT_OBJ("filter.layer"),
    0, 0,
    0, 0,
    0,
    CORE_OBJECT_NULL_INIT(0),
    CORE_OBJECT_ETHER_INIT(0),
    CORE_OBJECT_LOOP_INIT(0),
    CORE_OBJECT_LINUXSLL_INIT(0),
    CORE_OBJECT_LINUXSLL2_INIT(0),
    0, { CORE_OBJECT_IEEE802_INIT(0), CORE_OBJECT_IEEE802_INIT(0), CORE_OBJECT_IEEE802_INIT(0) },
    CORE_OBJECT_IP_INIT(0),
    CORE_OBJECT_IP6_INIT(0),
    CORE_OBJECT_GRE_INIT(0),
    CORE_OBJECT_ICMP_INIT(0),
    CORE_OBJECT_ICMP6_INIT(0),
    CORE_OBJECT_UDP_INIT(0),
    CORE_OBJECT_TCP_INIT(0),
    CORE_OBJECT_PAYLOAD_INIT(0)
};

core_log_t* filter_layer_log()
{
    return &_log;
}

void filter_layer_init(filter_layer_t* self)
{
    mlassert_self();

    *self = _defaults;
}

void filter_layer_destroy(filter_layer_t* self)
{
    mlassert_self();
}

#define need4x2(v1, v2, p, l) \
    if (l < 1) {              \
        break;                \
    }                         \
    v1 = (*p) >> 4;           \
    v2 = (*p) & 0xf;          \
    p += 1;                   \
    l -= 1

#define need8(v, p, l) \
    if (l < 1) {       \
        break;         \
    }                  \
    v = *p;            \
    p += 1;            \
    l -= 1

static inline uint16_t _need16(const void* ptr)
{
    uint16_t v;
    memcpy(&v, ptr, sizeof(v));
    return be16toh(v);
}

#define need16(v, p, l) \
    if (l < 2) {        \
        break;          \
    }                   \
    v = _need16(p);     \
    p += 2;             \
    l -= 2

#define needr16(v, p, l)      \
    if (l < 2) {              \
        break;                \
    }                         \
    v = bswap_16(_need16(p)); \
    p += 2;                   \
    l -= 2

static inline uint32_t _need32(const void* ptr)
{
    uint32_t v;
    memcpy(&v, ptr, sizeof(v));
    return be32toh(v);
}

#define need32(v, p, l) \
    if (l < 4) {        \
        break;          \
    }                   \
    v = _need32(p);     \
    p += 4;             \
    l -= 4

#define needr32(v, p, l)      \
    if (l < 4) {              \
        break;                \
    }                         \
    v = bswap_32(_need32(p)); \
    p += 4;                   \
    l -= 4

#define needxb(b, x, p, l) \
    if (l < x) {           \
        break;             \
    }                      \
    memcpy(b, p, x);       \
    p += x;                \
    l -= x

#define advancexb(x, p, l) \
    if (l < x) {           \
        break;             \
    }                      \
    p += x;                \
    l -= x

// static int _ip(filter_layer_t* self, const core_object_t* obj, const unsigned char* pkt, size_t len);

static inline int _proto(filter_layer_t* self, uint8_t proto, const core_object_t* obj, const unsigned char* pkt, size_t len)
{
    switch (proto) {
    case IPPROTO_GRE: {
        core_object_gre_t* gre = &self->gre;
        gre->obj_prev          = obj;

        need16(gre->gre_flags, pkt, len);
        need16(gre->ether_type, pkt, len);

        /* TODO: Incomplete, check RFC 1701 */

        self->produced = (core_object_t*)gre;

        // if (gre.gre_flags & 0x1) {
        //     need16(gre.checksum, pkt, len);
        // }
        // if (gre.gre_flags & 0x4) {
        //     need16(gre.key, pkt, len);
        // }
        // if (gre.gre_flags & 0x8) {
        //     need16(gre.sequence, pkt, len);
        // }
        //
        // switch (gre.ether_type) {
        // case ETHERTYPE_IP:
        // case ETHERTYPE_IPV6:
        //     return _ip(self, (core_object_t*)gre, pkt, len);
        //
        // default:
        //     break;
        // }
        break;
    }
    case IPPROTO_ICMP: {
        core_object_icmp_t* icmp = &self->icmp;
        icmp->obj_prev           = obj;

        need8(icmp->type, pkt, len);
        need8(icmp->code, pkt, len);
        need16(icmp->cksum, pkt, len);

        self->produced = (core_object_t*)icmp;
        break;
    }
    case IPPROTO_ICMPV6: {
        core_object_icmp6_t* icmp6 = &self->icmp6;
        icmp6->obj_prev            = obj;

        need8(icmp6->type, pkt, len);
        need8(icmp6->code, pkt, len);
        need16(icmp6->cksum, pkt, len);

        self->produced = (core_object_t*)icmp6;
        break;
    }
    case IPPROTO_UDP: {
        core_object_udp_t*     udp     = &self->udp;
        core_object_payload_t* payload = &self->payload;
        udp->obj_prev                  = obj;

        need16(udp->sport, pkt, len);
        need16(udp->dport, pkt, len);
        need16(udp->ulen, pkt, len);
        need16(udp->sum, pkt, len);

        payload->obj_prev = (core_object_t*)udp;

        /* Check for padding */
        if (len > (udp->ulen - 8)) {
            payload->padding = len - (udp->ulen - 8);
            payload->len     = len - payload->padding;
        } else {
            payload->padding = 0;
            payload->len     = len;
        }
        payload->payload = (uint8_t*)pkt;

        self->produced = (core_object_t*)payload;
        break;
    }
    case IPPROTO_TCP: {
        core_object_tcp_t*     tcp     = &self->tcp;
        core_object_payload_t* payload = &self->payload;
        tcp->obj_prev                  = obj;

        need16(tcp->sport, pkt, len);
        need16(tcp->dport, pkt, len);
        need32(tcp->seq, pkt, len);
        need32(tcp->ack, pkt, len);
        need4x2(tcp->off, tcp->x2, pkt, len);
        need8(tcp->flags, pkt, len);
        need16(tcp->win, pkt, len);
        need16(tcp->sum, pkt, len);
        need16(tcp->urp, pkt, len);
        if (tcp->off > 5) {
            tcp->opts_len = (tcp->off - 5) * 4;
            needxb(tcp->opts, tcp->opts_len, pkt, len);
        } else {
            tcp->opts_len = 0;
        }

        payload->obj_prev = (core_object_t*)tcp;

        /* Check for padding */
        if (obj->obj_type == CORE_OBJECT_IP && len > (((const core_object_ip_t*)obj)->len - (((const core_object_ip_t*)obj)->hl * 4) - (tcp->off * 4))) {
            payload->padding = len - (((const core_object_ip_t*)obj)->len - (((const core_object_ip_t*)obj)->hl * 4) - (tcp->off * 4));
            payload->len     = len - payload->padding;
        } else if (obj->obj_type == CORE_OBJECT_IP6 && len > (((const core_object_ip6_t*)obj)->plen - ((const core_object_ip6_t*)obj)->hlen - (tcp->off * 4))) {
            payload->padding = len - (((const core_object_ip6_t*)obj)->plen - ((const core_object_ip6_t*)obj)->hlen - (tcp->off * 4));
            payload->len     = len - payload->padding;
        } else {
            payload->padding = 0;
            payload->len     = len;
        }

        payload->payload = (uint8_t*)pkt;

        self->produced = (core_object_t*)payload;
        break;
    }
    default:
        self->produced = obj;
        break;
    }

    return 0;
}

static inline int _ip(filter_layer_t* self, const core_object_t* obj, const unsigned char* pkt, size_t len)
{
    if (len) {
        switch ((*pkt >> 4)) {
        case 4: {
            core_object_ip_t* ip = &self->ip;

            ip->obj_prev = obj;

            need4x2(ip->v, ip->hl, pkt, len);
            need8(ip->tos, pkt, len);
            need16(ip->len, pkt, len);
            need16(ip->id, pkt, len);
            need16(ip->off, pkt, len);
            need8(ip->ttl, pkt, len);
            need8(ip->p, pkt, len);
            need16(ip->sum, pkt, len);
            needxb(&ip->src, 4, pkt, len);
            needxb(&ip->dst, 4, pkt, len);

            /* TODO: IPv4 options */

            if (ip->hl < 5)
                break;
            if (ip->hl > 5) {
                advancexb((ip->hl - 5) * 4, pkt, len);
            }

            /* Check reported length for missing payload */
            if (ip->len < (ip->hl * 4)) {
                break;
            }
            if (len < (ip->len - (ip->hl * 4))) {
                break;
            }

            if (ip->off & 0x2000 || ip->off & 0x1fff) {
                core_object_payload_t* payload = &self->payload;

                payload->obj_prev = (core_object_t*)ip;

                /* Check for padding */
                if (len > (ip->len - (ip->hl * 4))) {
                    payload->padding = len - (ip->len - (ip->hl * 4));
                    payload->len     = len - payload->padding;
                } else {
                    payload->padding = 0;
                    payload->len     = len;
                }
                payload->payload = (uint8_t*)pkt;

                self->produced = (core_object_t*)payload;
                return 0;
            }

            return _proto(self, ip->p, (core_object_t*)ip, pkt, len);
        }
        case 6: {
            core_object_ip6_t* ip6 = &self->ip6;
            struct ip6_ext     ext;

            ip6->obj_prev = obj;
            ip6->is_frag = ip6->have_rtdst = 0;

            need32(ip6->flow, pkt, len);
            need16(ip6->plen, pkt, len);
            need8(ip6->nxt, pkt, len);
            need8(ip6->hlim, pkt, len);
            needxb(&ip6->src, 16, pkt, len);
            needxb(&ip6->dst, 16, pkt, len);
            ip6->hlen = 0;

            /* Check reported length for missing payload */
            if (len < ip6->plen) {
                break;
            }

            ext.ip6e_nxt = ip6->nxt;
            ext.ip6e_len = 0;
            while (ext.ip6e_nxt != IPPROTO_NONE
                   && ext.ip6e_nxt != IPPROTO_GRE
                   && ext.ip6e_nxt != IPPROTO_ICMPV6
                   && ext.ip6e_nxt != IPPROTO_UDP
                   && ext.ip6e_nxt != IPPROTO_TCP) {

                /*
                 * Advance to the start of next header, this may not be needed
                 * if it's the first header or if the header is supported.
                 */
                if (ext.ip6e_len) {
                    advancexb(ext.ip6e_len * 8, pkt, len);
                    ip6->hlen += ext.ip6e_len * 8;
                }

                /* TODO: Store IPv6 headers? */

                /* Handle supported headers */
                if (ext.ip6e_nxt == IPPROTO_FRAGMENT) {
                    if (ip6->is_frag) {
                        return 1;
                    }
                    need8(ext.ip6e_nxt, pkt, len);
                    need8(ext.ip6e_len, pkt, len);
                    if (ext.ip6e_len) {
                        return 1;
                    }
                    need16(ip6->frag_offlg, pkt, len);
                    need32(ip6->frag_ident, pkt, len);
                    ip6->is_frag = 1;
                } else if (ext.ip6e_nxt == IPPROTO_ROUTING) {
                    struct ip6_rthdr rthdr;

                    if (ip6->have_rtdst) {
                        return 1;
                    }

                    need8(ext.ip6e_nxt, pkt, len);
                    need8(ext.ip6e_len, pkt, len);
                    need8(rthdr.ip6r_type, pkt, len);
                    need8(rthdr.ip6r_segleft, pkt, len);
                    advancexb(4, pkt, len);

                    if (!rthdr.ip6r_type && rthdr.ip6r_segleft) {
                        if (ext.ip6e_len & 1) {
                            return 1;
                        }
                        if (ext.ip6e_len > 2) {
                            advancexb(ext.ip6e_len - 2, pkt, len);
                        }
                        needxb(ip6->rtdst, 16, pkt, len);
                        ip6->have_rtdst = 1;
                    }
                } else {
                    need8(ext.ip6e_nxt, pkt, len);
                    need8(ext.ip6e_len, pkt, len);
                    advancexb(6, pkt, len);
                }
            }

            if (ext.ip6e_nxt == IPPROTO_NONE || ip6->is_frag) {
                core_object_payload_t* payload = &self->payload;

                payload->obj_prev = (core_object_t*)ip6;

                /* Check for padding */
                if (len > (ip6->plen - ip6->hlen)) {
                    payload->padding = len - (ip6->plen - ip6->hlen);
                    payload->len     = len - payload->padding;
                } else {
                    payload->padding = 0;
                    payload->len     = len;
                }
                payload->payload = (uint8_t*)pkt;

                self->produced = (core_object_t*)payload;
                return 0;
            }

            return _proto(self, ext.ip6e_nxt, (core_object_t*)ip6, pkt, len);
        }
        default:
            break;
        }
    }

    self->produced = obj;

    return 0;
}

static inline int _ieee802(filter_layer_t* self, uint16_t tpid, const core_object_t* obj, const unsigned char* pkt, size_t len)
{
    core_object_ieee802_t* ieee802 = &self->ieee802[self->n_ieee802];
    uint16_t               tci;

    ieee802->obj_prev = obj;

    for (;;) {
        ieee802->tpid = tpid;
        need16(tci, pkt, len);
        ieee802->pcp = (tci & 0xe000) >> 13;
        ieee802->dei = (tci & 0x1000) >> 12;
        ieee802->vid = tci & 0x0fff;
        need16(ieee802->ether_type, pkt, len);

        switch (ieee802->ether_type) {
        case 0x88a8: /* 802.1ad */
        case 0x9100: /* 802.1 QinQ non-standard */
            self->n_ieee802++;
            if (self->n_ieee802 < N_IEEE802) {
                obj               = (const core_object_t*)ieee802;
                ieee802           = &self->ieee802[self->n_ieee802];
                ieee802->obj_prev = obj;
                tpid              = ieee802->ether_type;
                continue;
            }
            return 1;

        case ETHERTYPE_IP:
        case ETHERTYPE_IPV6:
            return _ip(self, (core_object_t*)ieee802, pkt, len);

        default:
            break;
        }
        break;
    }

    self->produced = obj;

    return 0;
}

static inline int _link(filter_layer_t* self, const core_object_pcap_t* pcap)
{
    const unsigned char* pkt;
    size_t               len;

    self->n_ieee802 = 0;

    pkt = pcap->bytes;
    len = pcap->caplen;

    switch (pcap->linktype) {
    case DLT_NULL: {
        core_object_null_t* null = &self->null;
        null->obj_prev           = (core_object_t*)pcap;

        if (pcap->is_swapped) {
            needr32(null->family, pkt, len);
        } else {
            need32(null->family, pkt, len);
        }

        switch (null->family) {
        case 2:
        case 24:
        case 28:
        case 30:
            return _ip(self, (core_object_t*)null, pkt, len);

        default:
            break;
        }
        break;
    }
    case DLT_EN10MB: {
        core_object_ether_t* ether = &self->ether;
        ether->obj_prev            = (core_object_t*)pcap;

        needxb(ether->dhost, 6, pkt, len);
        needxb(ether->shost, 6, pkt, len);
        need16(ether->type, pkt, len);

        switch (ether->type) {
        case 0x8100: /* 802.1q */
        case 0x88a8: /* 802.1ad */
        case 0x9100: /* 802.1 QinQ non-standard */
            return _ieee802(self, ether->type, (core_object_t*)ether, pkt, len);

        case ETHERTYPE_IP:
        case ETHERTYPE_IPV6:
            return _ip(self, (core_object_t*)ether, pkt, len);

        default:
            break;
        }
        break;
    }
    case DLT_LOOP: {
        core_object_loop_t* loop = &self->loop;
        loop->obj_prev           = (core_object_t*)pcap;

        need32(loop->family, pkt, len);

        switch (loop->family) {
        case 2:
        case 24:
        case 28:
        case 30:
            return _ip(self, (core_object_t*)loop, pkt, len);

        default:
            break;
        }
        break;
    }
    case DLT_RAW:
#ifdef DLT_IPV4
    case DLT_IPV4:
#endif
#ifdef DLT_IPV6
    case DLT_IPV6:
#endif
        return _ip(self, (core_object_t*)pcap, pkt, len);
    case DLT_LINUX_SLL: {
        core_object_linuxsll_t* linuxsll = &self->linuxsll;
        linuxsll->obj_prev               = (core_object_t*)pcap;

        need16(linuxsll->packet_type, pkt, len);
        need16(linuxsll->arp_hardware, pkt, len);
        need16(linuxsll->link_layer_address_length, pkt, len);
        needxb(linuxsll->link_layer_address, 8, pkt, len);
        need16(linuxsll->ether_type, pkt, len);

        switch (linuxsll->ether_type) {
        case 0x8100: /* 802.1q */
        case 0x88a8: /* 802.1ad */
        case 0x9100: /* 802.1 QinQ non-standard */
            return _ieee802(self, linuxsll->ether_type, (core_object_t*)linuxsll, pkt, len);

        case ETHERTYPE_IP:
        case ETHERTYPE_IPV6:
            return _ip(self, (core_object_t*)linuxsll, pkt, len);

        default:
            break;
        }
        break;
    }
    case DLT_LINUX_SLL2: {
        core_object_linuxsll2_t* linuxsll2 = &self->linuxsll2;
        linuxsll2->obj_prev                = (core_object_t*)pcap;

        need16(linuxsll2->protocol_type, pkt, len);
        need16(linuxsll2->reserved, pkt, len);
        need32(linuxsll2->interface_index, pkt, len);
        need16(linuxsll2->arphrd_type, pkt, len);
        need8(linuxsll2->packet_type, pkt, len);
        need8(linuxsll2->link_layer_address_length, pkt, len);
        needxb(linuxsll2->link_layer_address, 8, pkt, len);

        switch (linuxsll2->protocol_type) {
        case 0x8100: /* 802.1q */
        case 0x88a8: /* 802.1ad */
        case 0x9100: /* 802.1 QinQ non-standard */
            return _ieee802(self, linuxsll2->protocol_type, (core_object_t*)linuxsll2, pkt, len);

        case ETHERTYPE_IP:
        case ETHERTYPE_IPV6:
            return _ip(self, (core_object_t*)linuxsll2, pkt, len);

        default:
            break;
        }
        break;
    }
    /* TODO: These might be interesting to implement
        case DLT_IPNET:
        case DLT_PKTAP:
        */
    default:
        break;
    }

    self->produced = (core_object_t*)pcap;

    return 0;
}

static void _receive(filter_layer_t* self, const core_object_t* obj)
{
    mlassert_self();
    lassert(obj, "obj is nil");

    if (!self->recv) {
        lfatal("no receiver set");
#ifdef __clang_analyzer__
        return;
#endif
    }
    if (obj->obj_type != CORE_OBJECT_PCAP) {
        lfatal("obj is not CORE_OBJECT_PCAP");
    }

    if (!_link(self, (core_object_pcap_t*)obj)) {
        self->recv(self->ctx, self->produced);
    }
}

core_receiver_t filter_layer_receiver()
{
    return (core_receiver_t)_receive;
}

static const core_object_t* _produce(filter_layer_t* self)
{
    const core_object_t* obj;
    mlassert_self();

    obj = self->prod(self->prod_ctx);
    if (!obj || obj->obj_type != CORE_OBJECT_PCAP || _link(self, (core_object_pcap_t*)obj)) {
        return 0;
    }

    return self->produced;
}

core_producer_t filter_layer_producer(filter_layer_t* self)
{
    mlassert_self();

    if (!self->prod) {
        lfatal("no producer set");
    }

    return (core_producer_t)_produce;
}
