/*
 * 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 "core/object/dns.h"
#include "core/object/payload.h"
#include "core/assert.h"

#include <stdlib.h>
#include <string.h>
#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
#include <ctype.h>

#define _ERR_MALFORMED -2
#define _ERR_NEEDLABELS -3

static core_log_t        _log      = LOG_T_INIT("core.object.dns");
static core_object_dns_t _defaults = CORE_OBJECT_DNS_INIT(0);

static core_object_dns_label_t _defaults_label = { 0 };
static core_object_dns_rr_t    _defaults_rr    = { 0 };
static core_object_dns_q_t     _defaults_q     = { 0 };

core_log_t* core_object_dns_log()
{
    return &_log;
}

core_object_dns_t* core_object_dns_new()
{
    core_object_dns_t* self;

    mlfatal_oom(self = malloc(sizeof(core_object_dns_t)));
    *self = _defaults;

    return self;
}

core_object_dns_t* core_object_dns_copy(const core_object_dns_t* self)
{
    core_object_dns_t* copy;
    mlassert_self();

    mlfatal_oom(copy = malloc(sizeof(core_object_dns_t)));
    memcpy(copy, self, sizeof(core_object_dns_t));
    copy->obj_prev = 0;

    return (core_object_dns_t*)copy;
}

void core_object_dns_free(core_object_dns_t* self)
{
    mlassert_self();
    free(self);
}

void core_object_dns_reset(core_object_dns_t* self)
{
    mlassert_self();
    *self = _defaults;
}

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

static inline uint16_t _need16(const void* ptr)
{
#ifdef __clang_analyzer__
    if (!ptr)
        return 0;
#endif
    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

static inline uint32_t _need32(const void* ptr)
{
#ifdef __clang_analyzer__
    if (!ptr)
        return 0;
#endif
    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 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

int core_object_dns_parse_header(core_object_dns_t* self)
{
    const core_object_payload_t* payload;
    uint8_t                      byte;
    mlassert_self();

    if (!(payload = (core_object_payload_t*)self->obj_prev) || payload->obj_type != CORE_OBJECT_PAYLOAD) {
        mlfatal("no obj_prev or invalid type");
#ifdef __clang_analyzer__
        return -1;
#endif
    }
    if (!payload->payload || !payload->len) {
        mlfatal("no payload set or zero length");
    }

    self->payload = self->at = payload->payload;
    self->len = self->left = payload->len;

    for (;;) {
        if (self->includes_dnslen) {
            need16(self->dnslen, self->at, self->left);
            self->have_dnslen = 1;
        }
        need16(self->id, self->at, self->left);
        self->have_id = 1;

        need8(byte, self->at, self->left);
        self->qr      = byte & (1 << 7) ? 1 : 0;
        self->opcode  = (byte >> 3) & 0xf;
        self->aa      = byte & (1 << 2) ? 1 : 0;
        self->tc      = byte & (1 << 1) ? 1 : 0;
        self->rd      = byte & (1 << 0) ? 1 : 0;
        self->have_qr = self->have_opcode = self->have_aa = self->have_tc = self->have_rd = 1;

        need8(byte, self->at, self->left);
        self->ra      = byte & (1 << 7) ? 1 : 0;
        self->z       = byte & (1 << 6) ? 1 : 0;
        self->ad      = byte & (1 << 5) ? 1 : 0;
        self->cd      = byte & (1 << 4) ? 1 : 0;
        self->rcode   = byte & 0xf;
        self->have_ra = self->have_z = self->have_ad = self->have_cd = self->have_rcode = 1;

        need16(self->qdcount, self->at, self->left);
        self->have_qdcount = 1;

        need16(self->ancount, self->at, self->left);
        self->have_ancount = 1;

        need16(self->nscount, self->at, self->left);
        self->have_nscount = 1;

        need16(self->arcount, self->at, self->left);
        self->have_arcount = 1;

        return 0;
    }

    // TODO: error here on malformed/truncated? could be quite spammy
    return _ERR_MALFORMED;
}

static inline size_t _rdata_labels(uint16_t type)
{
    switch (type) {
    case CORE_OBJECT_DNS_TYPE_NS:
    case CORE_OBJECT_DNS_TYPE_MD:
    case CORE_OBJECT_DNS_TYPE_MF:
    case CORE_OBJECT_DNS_TYPE_CNAME:
    case CORE_OBJECT_DNS_TYPE_MB:
    case CORE_OBJECT_DNS_TYPE_MG:
    case CORE_OBJECT_DNS_TYPE_MR:
    case CORE_OBJECT_DNS_TYPE_PTR:
    case CORE_OBJECT_DNS_TYPE_NXT:
    case CORE_OBJECT_DNS_TYPE_DNAME:
    case CORE_OBJECT_DNS_TYPE_NSEC:
    case CORE_OBJECT_DNS_TYPE_TKEY:
    case CORE_OBJECT_DNS_TYPE_TSIG:
        return 1;

    case CORE_OBJECT_DNS_TYPE_SOA:
    case CORE_OBJECT_DNS_TYPE_MINFO:
    case CORE_OBJECT_DNS_TYPE_RP:
    case CORE_OBJECT_DNS_TYPE_TALINK:
        return 2;

    case CORE_OBJECT_DNS_TYPE_MX:
    case CORE_OBJECT_DNS_TYPE_AFSDB:
    case CORE_OBJECT_DNS_TYPE_RT:
    case CORE_OBJECT_DNS_TYPE_KX:
    case CORE_OBJECT_DNS_TYPE_LP:
        return 1;

    case CORE_OBJECT_DNS_TYPE_PX:
        return 2;

    case CORE_OBJECT_DNS_TYPE_SIG:
    case CORE_OBJECT_DNS_TYPE_RRSIG:
        return 1;

    case CORE_OBJECT_DNS_TYPE_SRV:
        return 1;

    case CORE_OBJECT_DNS_TYPE_NAPTR:
        return 1;

    case CORE_OBJECT_DNS_TYPE_HIP:
        return 1;

    default:
        break;
    }

    return 0;
}

static inline size_t _label(core_object_dns_t* self, core_object_dns_label_t* label, size_t labels)
{
    size_t n;

    for (n = 0; self->left && n < labels; n++) {
        core_object_dns_label_t* l = &label[n];
        *l                         = _defaults_label;

        need8(l->length, self->at, self->left);

        if ((l->length & 0xc0) == 0xc0) {
            need8(l->offset, self->at, self->left);
            l->offset |= (l->length & 0x3f) << 8;
            l->have_offset = 1;
            return n;
        } else if (l->length & 0xc0) {
            l->extension_bits      = l->length >> 6;
            l->have_extension_bits = 1;
            return n;
        } else if (l->length) {
            l->have_length = 1;

            l->offset = self->at - self->payload - 1;
            advancexb(l->length, self->at, self->left);
            l->have_dn = 1;
        } else {
            l->is_end = 1;
            return n;
        }
    }

    return n;
}

int core_object_dns_parse_q(core_object_dns_t* self, core_object_dns_q_t* q, core_object_dns_label_t* label, size_t labels)
{
    mlassert_self();
    mlassert(q, "q is nil");
    mlassert(label, "label is nil");
    mlassert(labels, "labels is zero");
    mlassert(self->at, "at is nil");

    for (;;) {
        *q        = _defaults_q;
        q->labels = _label(self, label, labels);
        if (q->labels < labels) {
            core_object_dns_label_t* l = &label[q->labels];
            if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
                // TODO: error here on malformed/truncated? could be quite spammy
                return _ERR_MALFORMED;
            }
        } else {
            mlwarning("need more labels, aborting DNS parsing");
            return _ERR_NEEDLABELS;
        }
        q->labels++;

        need16(q->type, self->at, self->left);
        q->have_type = 1;

        need16(q->class, self->at, self->left);
        q->have_class = 1;

        return 0;
    }

    // TODO: error here on malformed/truncated? could be quite spammy
    return _ERR_MALFORMED;
}

int core_object_dns_parse_rr(core_object_dns_t* self, core_object_dns_rr_t* rr, core_object_dns_label_t* label, size_t labels)
{
    size_t rdata_label_sets;
    mlassert_self();
    mlassert(rr, "rr is nil");
    mlassert(label, "label is nil");
    mlassert(labels, "labels is zero");
    mlassert(self->at, "at is nil");

    for (;;) {
        *rr        = _defaults_rr;
        rr->labels = _label(self, label, labels);
        if (rr->labels < labels) {
            core_object_dns_label_t* l = &label[rr->labels];
            if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
                // TODO: error here on malformed/truncated? could be quite spammy
                return _ERR_MALFORMED;
            }
        } else {
            mlwarning("need more labels, aborting DNS parsing");
            return _ERR_NEEDLABELS;
        }
        rr->labels++;

        need16(rr->type, self->at, self->left);
        rr->have_type = 1;

        need16(rr->class, self->at, self->left);
        rr->have_class = 1;

        need32(rr->ttl, self->at, self->left);
        rr->have_ttl = 1;

        need16(rr->rdlength, self->at, self->left);
        rr->have_rdlength = 1;

        rr->rdata_offset = self->at - self->payload;
        if (!(rdata_label_sets = _rdata_labels(rr->type))) {
            advancexb(rr->rdlength, self->at, self->left);
            rr->have_rdata = 1;
            return 0;
        }

        switch (rr->type) {
        case CORE_OBJECT_DNS_TYPE_MX:
        case CORE_OBJECT_DNS_TYPE_AFSDB:
        case CORE_OBJECT_DNS_TYPE_RT:
        case CORE_OBJECT_DNS_TYPE_KX:
        case CORE_OBJECT_DNS_TYPE_LP:
        case CORE_OBJECT_DNS_TYPE_PX:
            advancexb(2, self->at, self->left);
            break;

        case CORE_OBJECT_DNS_TYPE_SIG:
        case CORE_OBJECT_DNS_TYPE_RRSIG:
            advancexb(18, self->at, self->left);
            break;

        case CORE_OBJECT_DNS_TYPE_SRV:
            advancexb(6, self->at, self->left);
            break;

        case CORE_OBJECT_DNS_TYPE_NAPTR: {
            uint8_t naptr_length;

            advancexb(4, self->at, self->left);
            need8(naptr_length, self->at, self->left);
            advancexb(naptr_length, self->at, self->left);
            need8(naptr_length, self->at, self->left);
            advancexb(naptr_length, self->at, self->left);
            need8(naptr_length, self->at, self->left);
            advancexb(naptr_length, self->at, self->left);
        } break;

        case CORE_OBJECT_DNS_TYPE_HIP: {
            uint8_t  hit_length;
            uint16_t pk_length;

            need8(hit_length, self->at, self->left);
            advancexb(1, self->at, self->left);
            need16(pk_length, self->at, self->left);
            advancexb(hit_length, self->at, self->left);
            advancexb(pk_length, self->at, self->left);

            if (self->at - self->payload >= rr->rdata_offset + rr->rdlength) {
                rdata_label_sets = 0;
            }
        } break;
        }

        while (rdata_label_sets) {
            rr->rdata_labels += _label(self, &label[rr->labels + rr->rdata_labels], labels - rr->labels - rr->rdata_labels);
            if (rr->labels + rr->rdata_labels < labels) {
                core_object_dns_label_t* l = &label[rr->labels + rr->rdata_labels];
                if (!(l->have_offset | l->have_extension_bits | l->is_end)) {
                    // TODO: error here on malformed/truncated? could be quite spammy
                    return _ERR_MALFORMED;
                }
            } else {
                mlwarning("need more labels, aborting DNS parsing");
                return _ERR_NEEDLABELS;
            }
            rr->rdata_labels++;

            if (rr->type == CORE_OBJECT_DNS_TYPE_HIP && self->at - self->payload < rr->rdata_offset + rr->rdlength) {
                continue;
            }

            rdata_label_sets--;
        }

        if (self->at - self->payload < rr->rdata_offset + rr->rdlength) {
            rr->padding_offset = self->at - self->payload;
            rr->padding_length = rr->rdlength - (rr->padding_offset - rr->rdata_offset);

            advancexb(rr->padding_length, self->at, self->left);

            /*
             * TODO:
             *
             * This can indicate padding but we do not set that we have padding
             * yet because we need to fully understand all record types before
             * that and process valid data after the labels
             *
            rr->have_padding = 1;
             */
        } else if (self->at - self->payload > rr->rdata_offset + rr->rdlength) {
            // TODO: error here on malformed/truncated? could be quite spammy
            return _ERR_MALFORMED;
        }
        rr->have_rdata = 1;

        return 0;
    }

    // TODO: error here on malformed/truncated? could be quite spammy
    return _ERR_MALFORMED;
}

const char* core_object_dns_torfc1035(const char* str, size_t len)
{
    mlassert(str, "str is nil");
    static char res[512];

    size_t        i, j = 0;
    unsigned char c;
    for (i = 0; i < len; i++) {
        c = (unsigned char)str[i];

        if (j + 4 >= sizeof(res)) {
            return 0;
        }

        if (isalnum(c) || c == '-' || c == '_') {
            res[j++] = c;
        } else if (isprint(c)) {
            res[j++] = '\\';
            res[j++] = c;
        } else {
            res[j++] = '\\';
            res[j++] = '0' + (c / 100);
            res[j++] = '0' + ((c / 10) % 10);
            res[j++] = '0' + (c % 10);
        }
    }

    res[j] = 0;

    return res;
}