/*
 * Copyright (c) 2019-2021, OARC, Inc.
 * Copyright (c) 2019, DENIC eG
 * All rights reserved.
 *
 * This file is part of dnsmeter.
 *
 * dnsmeter 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.
 *
 * dnsmeter 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 dnsmeter.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "query.h"
#include "exceptions.h"

#include <netinet/in.h>
#include <string.h>
#include <resolv.h>
#include <netdb.h>
#include <sys/time.h>

static const char* rr_types[] = {
    "A", "AAAA", "MX", "NS", "DS", "DNSKEY", "TXT", "SOA", "NAPTR", "RRSIG",
    "NSEC", "NSEC3", "NSEC3PARAM", "PTR", "SRV",
    "CNAME", "TSIG", "*", "ANY", "AXFR", "IXFR",
    "SPF", "A6", "HINFO", "WKS", "NULL",
    NULL
};

static int rr_code[] = {
    1, 28, 15, 2, 43, 48, 16, 6, 35, 46,
    47, 50, 51, 12, 33,
    5, 250, 255, 255, 252, 251,
    99, 38, 13, 11, 10,
    0
};

#pragma pack(push) /* push current alignment to stack */
#pragma pack(1) /* set alignment to 1 byte boundary */
struct DNS_OPT {
    unsigned char  name;
    unsigned short type;
    unsigned short udp_payload_size;
    unsigned char  extended_rcode;
    unsigned char  edns0_version;
    unsigned short z;
    unsigned short data_length;
};
#pragma pack(pop) /* restore original alignment from stack */

int MakeQuery(const ppl7::String& query, unsigned char* buffer, size_t buffersize, bool dnssec, int udp_payload_size)
{
    ppl7::Array tok(query, " ");
    if (tok.size() != 2)
        throw InvalidDNSQuery(query);
    ppl7::String Type = tok[1].toUpperCase();

    int         t   = 0;
    const char* str = Type.c_str();
    while (rr_types[t] != NULL) {
        if (!strcmp(str, rr_types[t])) {
            int bytes = res_mkquery(QUERY,
                (const char*)tok[0],
                C_IN,
                rr_code[t],
                NULL, 0, NULL, buffer, (int)buffersize);
            if (bytes < 0)
                throw InvalidDNSQuery("%s", hstrerror(h_errno));
            if (!dnssec)
                return bytes;
            return AddDnssecToQuery(buffer, buffersize, bytes, udp_payload_size);
        }
        t++;
    }
    throw UnknownRRType(tok[1]);
}

int AddDnssecToQuery(unsigned char* buffer, size_t buffersize, int querysize, int udp_payload_size)
{
    DNS_HEADER* dns = (DNS_HEADER*)buffer;
    dns->ad         = 1;
    dns->add_count  = htons(1);
    DNS_OPT* opt    = (DNS_OPT*)(buffer + querysize);
    memset(opt, 0, 11);
    opt->type             = htons(41);
    opt->udp_payload_size = htons(udp_payload_size);
    opt->z                = htons(0x8000); // DO-bit
    return querysize + 11;
}

unsigned short getQueryTimestamp()
{
    struct timeval tp;
    if (gettimeofday(&tp, NULL) == 0) {
        return (tp.tv_sec % 6) * 10000 + (tp.tv_usec / 100);
    }
    return 0;
}

double getQueryRTT(unsigned short start)
{
    unsigned short now  = getQueryTimestamp();
    unsigned short diff = now - start;
    if (now < start)
        diff = 60000 - start + now;
    return (double)(diff) / 10000.0f;
}
