/* dso-utils.c
 *
 * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <netinet/in.h>
#include "dns-msg.h"
#include "ioloop.h"
#include "dso-utils.h"
#include "dso.h"

void
dso_simple_response(comm_t *comm, message_t *message, const dns_wire_t *wire, int rcode)
{
    struct iovec iov;
    dns_wire_t response;
    memset(&response, 0, DNS_HEADER_SIZE);

    // We take the ID and the opcode from the incoming message, because if the
    // header has been mangled, we (a) wouldn't have gotten here and (b) don't
    // have any better choice anyway.
    response.id = wire->id;
    dns_qr_set(&response, dns_qr_response);
    dns_opcode_set(&response, dns_opcode_get(wire));
    dns_rcode_set(&response, rcode);

    size_t length = DNS_HEADER_SIZE;
    uint16_t wire_length = message != NULL ? message->length : (uint16_t)sizeof(*wire);
    dns_rr_t question;
    memset(&question, 0, sizeof(question));
    unsigned offp = 0;
    if (ntohs(wire->qdcount) == 1 &&
        dns_rr_parse(&question, wire->data, wire_length - DNS_HEADER_SIZE, &offp, false, false))
    {
        dns_towire_state_t towire;
        memset(&towire, 0, sizeof(towire));
        towire.p = &response.data[0];
        towire.lim = ((uint8_t *)&response) + sizeof(response);
        towire.message = &response;

        size_t namelen = dns_name_to_wire_canonical(towire.p, towire.lim - towire.p, question.name);
        if (namelen != 0) {
            towire.p += namelen;
            dns_u16_to_wire(&towire, question.type);
            dns_u16_to_wire(&towire, question.qclass);
            if (!towire.truncated && !towire.error) {
                response.qdcount = htons(1);
                length += towire.p - (uint8_t *)&response.data[0];
            }
        }
        dns_name_free(question.name);
    }
    iov.iov_base = &response;
    iov.iov_len = length; // No RRs
    ioloop_send_message(comm, message, &iov, 1);
}

bool
dso_send_simple_response(dso_state_t *dso, int rcode, const dns_wire_t *header, const char *UNUSED rcode_name)
{
    dso_simple_response((comm_t *)dso->transport, NULL, header, rcode);
    return true;
}

bool
dso_send_formerr(dso_state_t *dso, const dns_wire_t *header)
{
    comm_t *transport = dso->transport;
    (void)header;
    dso_simple_response(transport, NULL, header, dns_rcode_formerr);
    return true;
}

void
dso_retry_delay_response(comm_t *comm, message_t *message, const dns_wire_t *wire, int rcode, uint32_t milliseconds)
{
    dns_wire_t response;
    dns_towire_state_t towire;
    struct iovec iov;
    memset(&response, 0, DNS_HEADER_SIZE);
    memset(&towire, 0, sizeof(towire));

    towire.p = &response.data[0];  // We start storing RR data here.
    towire.lim = ((uint8_t *)&response) + sizeof(response);
    towire.message = &response;
    towire.p_rdlength = NULL;
    towire.p_opt = NULL;

    response.id = wire->id;
    dns_qr_set(&response, dns_qr_response);
    dns_opcode_set(&response, dns_opcode_get(wire));
    dns_rcode_set(&response, rcode);

    dns_u16_to_wire(&towire, kDSOType_RetryDelay);
    // This shouldn't be possible.
    if (towire.p + 2 > towire.lim) {
        FAULT("No room for dso length in Retry Delay message.");
        return;
    }
    uint8_t *p_dso_length = towire.p;
    towire.p += 2;

    dns_u32_to_wire(&towire, milliseconds);

    int16_t dso_length = towire.p - p_dso_length - 2;
    iov.iov_len = (towire.p - (uint8_t *)&response);
    iov.iov_base = &response;

    towire.p = p_dso_length;
    dns_u16_to_wire(&towire, dso_length);
    ioloop_send_message(comm, message, &iov, 1);
}

int32_t
dso_transport_idle(void * UNUSED context, int32_t UNUSED now, int32_t next_event)
{
    return next_event;
}

// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: