/* LICENSE_BEGIN

    MIT License

    SPDX:MIT

    https://spdx.org/licenses/MIT

    See LICENSE.txt file in the top level directory.

LICENSE_END */


#include "zed5/dns/protocol.h"

#include <sys/time.h>
#include <sstream>

#include "zed5/core/base/bits.h"
#include "zed5/core/encoding/hex.h"
#include "zed5/dns/constants.h"

using org::zed5::core::BitOps;
using org::zed5::core::ByteView;
using org::zed5::core::ByteSerializer;


namespace org {
namespace zed5 {
namespace dns {

namespace {

int getQueryBit(uint8_t byte2) {
  return BitOps<uint8_t>(byte2).get(7);
}

}  // namespace


Header::Header(ByteView bytes) {
    if (bytes.size() >= kSize) {
        bytes_ = bytes;
    }
}

uint16_t Header::id() const {
    uint16_t rval = 0;
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).readNet16(&rval);
    }
    return rval;
}

Header& Header::id(uint16_t id) {
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).writeNet16(id);
    }
    return *this;
}

bool Header::isQuery() const {
    return bytes_.empty() ? false : (getQueryBit(bytes_.u_ptr()[2]) == 0);
}

bool Header::isResponse() const {
    return bytes_.empty() ? false : (getQueryBit(bytes_.u_ptr()[2]) == 1);
}

OpCode Header::opCode() const {
    const uint8_t rval = bytes_.empty() ? 0 : bytes_.u_ptr()[2];
    return static_cast<OpCode>((rval >> 3) & 0x0f);
}

// ...

ResponseCode Header::responseCode() const {
    const uint8_t rval = bytes_.empty() ? 0 : bytes_.u_ptr()[3];
    return static_cast<ResponseCode>(rval & 0x0f);
}

uint16_t Header::questionCount() const {
    return ByteSerializer(bytes_).jumpTo(4).readNet16();
}

Header& Header::questionCount(uint16_t count) {
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).jumpTo(4).writeNet16(count);
    }
    return *this;
}

uint16_t Header::answerCount() const {
    return ByteSerializer(bytes_).jumpTo(6).readNet16();
}

Header& Header::answerCount(uint16_t count) {
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).jumpTo(6).writeNet16(count);
    }
    return *this;
}

uint16_t Header::nameserverCount() const {
    return ByteSerializer(bytes_).jumpTo(8).readNet16();
}

Header& Header::nameserverCount(uint16_t count) {
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).jumpTo(8).writeNet16(count);
    }
    return *this;
}

uint16_t Header::additionalCount() const {
    return ByteSerializer(bytes_).jumpTo(10).readNet16();
}

Header& Header::additionalCount(uint16_t count) {
    if (!bytes_.empty()) {
        ByteSerializer(bytes_).jumpTo(10).writeNet16(count);
    }
    return *this;
}

namespace {

RR* parseResourceRecord(
        ByteView whole_packet, const bool isQuery, ByteView* rr_start) {
    if (rr_start == nullptr ||
        whole_packet.empty() ||
        !whole_packet.contains(*rr_start)) {
        return nullptr;
    }

    RR tmp_rr;
    size_t pos = 0;
    const uint8_t kMask = 0xc0;  // 1100 0000
    while (pos < rr_start->size()) {
        if ((*rr_start).u_ptr()[pos] == 0U) { break; }  // end of a label
        if (((*rr_start).u_ptr()[pos] & kMask) == kMask) {
            pos++;  // label compression, 2 bytes
            break;
        }
        // TODO(ek): Actually read the name.
        pos++;
    }
    pos++;

    const size_t kReqdSize =
            isQuery ? RR::kQueryFixedSize : RR::kReplyFixedSize;

    if (pos + kReqdSize - 1 >= rr_start->size()) { return nullptr; }
    ByteSerializer cursor(rr_start->slice()
                                   .fromIndex(pos)
                                   .ofSize(kReqdSize));
    pos += kReqdSize;
    tmp_rr.rr_type = static_cast<dns::RRType>(cursor.readNet16());
    tmp_rr.rr_class = static_cast<dns::RRClass>(cursor.readNet16());
    if (!isQuery) {
        tmp_rr.ttl = cursor.readNet32();
        const uint16_t rdlength = cursor.readNet16();
        tmp_rr.rdata = rr_start->slice()
                           .fromIndex(pos)
                           .ofSize(static_cast<size_t>(rdlength));
        // TODO(ek): validate reported and actual lengths.
        pos += rdlength;
    }

    *rr_start = rr_start->slice().fromIndex(pos).toEnd();
    return new RR(tmp_rr);
}

}  // namespace

RR* RR::parseQueryRecord(ByteView whole_packet, ByteView* rr_start) {
    return parseResourceRecord(whole_packet, true, rr_start);
}

RR* RR::parseReplyRecord(ByteView whole_packet, ByteView* rr_start) {
    return parseResourceRecord(whole_packet, false, rr_start);
}

RR::RR()
    : rr_type(RRType::NIL),
      rr_class(RRClass::IN),
      ttl(0)
      {}

}  // namespace dns
}  // namespace zed5
}  // namespace org
