/*  Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    This program 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.

    This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/*!
 * \file
 *
 * \brief Domain name structure and API for manipulating it.
 *
 * \addtogroup dname
 * @{
 */

#pragma once

#include <stdint.h>
#include <stdbool.h>

#include "libknot/attribute.h"
#include "libknot/consts.h"
#include "libknot/mm_ctx.h"

/*! \brief Type representing a domain name in wire format. */
typedef uint8_t knot_dname_t;

/*! \brief Local domain name storage. */
typedef uint8_t knot_dname_storage_t[KNOT_DNAME_MAXLEN];

/*! \brief Local textual domain name storage. */
typedef char knot_dname_txt_storage_t[KNOT_DNAME_TXT_MAXLEN + 1];

/*!
 * \brief Check dname on the wire for constraints.
 *
 * If the name passes such checks, it is safe to be used in rest of the functions.
 *
 * \param name  Name on the wire.
 * \param endp  Name boundary.
 * \param pkt   Wire.
 *
 * \retval (compressed) size of the domain name.
 * \retval KNOT_EINVAL
 * \retval KNOT_EMALF
 */
_pure_ _mustcheck_
int knot_dname_wire_check(const uint8_t *name, const uint8_t *endp,
                          const uint8_t *pkt);

/*!
 * \brief Duplicates the given domain name to a local storage.
 *
 * \param dst   Destination storage.
 * \param name  Domain name to be copied.
 *
 * \retval size of the domain name.
 * \retval 0 if invalid argument.
 */
_mustcheck_
size_t knot_dname_store(knot_dname_storage_t dst, const knot_dname_t *name);

/*!
 * \brief Duplicates the given domain name.
 *
 * \param name  Domain name to be copied.
 * \param mm    Memory context.
 *
 * \return New domain name which is an exact copy of \a name.
 */
_mustcheck_
knot_dname_t *knot_dname_copy(const knot_dname_t *name, knot_mm_t *mm);

/*!
 * \brief Copy name to wire as is, no compression pointer expansion will be done.
 *
 * \param dst     Destination wire.
 * \param src     Source name.
 * \param maxlen  Maximum wire length.
 *
 * \return the number of bytes written or negative error code
 */
int knot_dname_to_wire(uint8_t *dst, const knot_dname_t *src, size_t maxlen);

/*!
 * \brief Write unpacked name (i.e. compression pointers expanded)
 *
 * \note The function is very similar to the knot_dname_to_wire(), except
 *       it expands compression pointers. E.g. you want to use knot_dname_unpack()
 *       if you copy a dname from incoming packet to some persistent storage.
 *       And you want to use knot_dname_to_wire() if you know the name is not
 *       compressed or you want to copy it 1:1.
 *
 * \param dst     Destination wire.
 * \param src     Source name.
 * \param maxlen  Maximum destination wire size.
 * \param pkt     Name packet wire (for compression pointers).
 *
 * \return number of bytes written
 */
int knot_dname_unpack(uint8_t *dst, const knot_dname_t *src,
                      size_t maxlen, const uint8_t *pkt);

/*!
 * \brief Converts the given domain name to its string representation.
 *
 * \note Output buffer is allocated automatically if dst is NULL.
 *
 * \param dst     Output buffer.
 * \param name    Domain name to be converted.
 * \param maxlen  Output buffer length.
 *
 * \return 0-terminated string if successful, NULL if error.
 */
char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen);

/*!
 * \brief This function is a shortcut for \ref knot_dname_to_str with
 *        no output buffer parameters.
 */
_mustcheck_
static inline char *knot_dname_to_str_alloc(const knot_dname_t *name)
{
	return knot_dname_to_str(NULL, name, 0);
}

/*!
 * \brief Creates a dname structure from domain name given in presentation
 *        format.
 *
 * \note The resulting FQDN is stored in the wire format.
 * \note Output buffer is allocated automatically if dst is NULL.
 *
 * \param dst    Output buffer.
 * \param name   Domain name in presentation format (labels separated by dots,
 *               '\0' terminated).
 * \param maxlen Output buffer length.
 *
 * \return New dname if successful, NULL if error.
 */
knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen);

/*!
 * \brief This function is a shortcut for \ref knot_dname_from_str with
 *        no output buffer parameters.
 */
_mustcheck_
static inline knot_dname_t *knot_dname_from_str_alloc(const char *name)
{
	return knot_dname_from_str(NULL, name, 0);
}

/*!
 * \brief Convert domain name to lowercase.
 *
 * \param name  Domain name to be converted.
 */
void knot_dname_to_lower(knot_dname_t *name);

/*!
 * \brief Copy lowercased domain name.
 *
 * \note The output buffer isn't checked if it's big enough!
 *
 * \param dst   Destination buffer.
 * \param name  Source domain name to be converted.
 */
void knot_dname_copy_lower(knot_dname_t *dst, const knot_dname_t *name);

/*!
 * \brief Returns size of the given domain name.
 *
 * \note If the domain name is compressed, the length of not compressed part
 *       is returned.
 *
 * \param name  Domain name to get the size of.
 *
 * \retval size of the domain name.
 * \retval 0 if invalid argument.
 */
_pure_
size_t knot_dname_size(const knot_dname_t *name);

/*!
 * \brief Returns full size of the given domain name (expanded compression ptrs).
 *
 * \param name  Domain name to get the size of.
 * \param pkt   Related packet (or NULL if unpacked)
 *
 * \retval size of the domain name.
 * \retval 0 if invalid argument.
 */
_pure_
size_t knot_dname_realsize(const knot_dname_t *name, const uint8_t *pkt);

/*!
 * \brief Checks if the domain name is a wildcard.
 *
 * \param name  Domain name to check.
 *
 * \retval true if \a dname is a wildcard domain name.
 * \retval false otherwise.
 */
static inline
bool knot_dname_is_wildcard(const knot_dname_t *name)
{
	return name != NULL && name[0] == 1 && name[1] == '*';
}

/*!
 * \brief Returns the number of labels common for the two domain names (counted
 *        from the rightmost label.
 *
 * \param d1  First domain name.
 * \param d2  Second domain name.
 *
 * \return Number of labels common for the two domain names.
 */
_pure_
size_t knot_dname_matched_labels(const knot_dname_t *d1, const knot_dname_t *d2);

/*!
 * \brief Replaces the suffix of given size in one domain name with other domain
 *        name.
 *
 * \param name    Domain name where to replace the suffix.
 * \param labels  Size of the suffix to be replaced.
 * \param suffix  New suffix to be used as a replacement.
 * \param mm      Memory context.
 *
 * \return New domain name created by replacing suffix of \a dname of size
 *         \a size with \a suffix.
 */
_mustcheck_
knot_dname_t *knot_dname_replace_suffix(const knot_dname_t *name, unsigned labels,
                                        const knot_dname_t *suffix, knot_mm_t *mm);

/*!
 * \brief Destroys the given domain name.
 *
 * \param name  Domain name to be destroyed.
 * \param mm    Memory context.
 */
void knot_dname_free(knot_dname_t *name, knot_mm_t *mm);

/*!
 * \brief Compares two domain names by labels (case sensitive).
 *
 * \param d1  First domain name.
 * \param d2  Second domain name.
 *
 * \retval < 0 if \a d1 goes before \a d2 in canonical order.
 * \retval > 0 if \a d1 goes after \a d2 in canonical order.
 * \retval 0 if the domain names are identical.
 */
_pure_
int knot_dname_cmp(const knot_dname_t *d1, const knot_dname_t *d2);

/*!
 * \brief Compares two domain names (case sensitive).
 *
 * \param d1  First domain name.
 * \param d2  Second domain name.
 *
 * \retval true if the domain names are identical
 * \retval false if the domain names are NOT identical
 */
_pure_
bool knot_dname_is_equal(const knot_dname_t *d1, const knot_dname_t *d2);

/*!
 * \brief Compares two domain names (case insensitive).
 *
 * \param d1  First domain name.
 * \param d2  Second domain name.
 *
 * \retval true if the domain names are equal
 * \retval false if the domain names are NOT equal
 */
_pure_
bool knot_dname_is_case_equal(const knot_dname_t *d1, const knot_dname_t *d2);

/*!
 * \brief Count length of the N first labels.
 *
 * \param name     Domain name.
 * \param nlabels  First N labels.
 * \param pkt      Related packet (or NULL if not compressed).
 *
 * \return Length of the prefix.
 */
_pure_
size_t knot_dname_prefixlen(const uint8_t *name, unsigned nlabels, const uint8_t *pkt);

/*!
 * \brief Return number of labels in the domain name.
 *
 * Terminal nullbyte is not counted.
 *
 * \param name  Domain name.
 * \param pkt   Related packet (or NULL if not compressed).
 *
 * \return Number of labels.
 */
_pure_
size_t knot_dname_labels(const uint8_t *name, const uint8_t *pkt);

/*!
 * \brief Convert domain name from wire to the lookup format.
 *
 * Formats names from rightmost label to the leftmost, separated by the lowest
 * possible character (\\x00). Sorting such formatted names also gives
 * correct canonical order (for NSEC/NSEC3). The first byte of the output
 * contains length of the remaining output.
 *
 * Examples:
 * Name:   lake.example.com.
 * Wire:   \\x04lake\\x07example\\x03com\\x00
 * Lookup: \\x11com\\x00example\\x00lake\\x00
 *
 * Name:   .
 * Wire:   \\x00
 * Lookup: \\x00
 *
 * \param src      Source domain name.
 * \param storage  Memory to store converted name into. Don't use directly!
 *
 * \retval Lookup format if successful (pointer into the storage).
 * \retval NULL on invalid parameters.
 */
uint8_t *knot_dname_lf(const knot_dname_t *src, knot_dname_storage_t storage);

/*!
 * \brief Check whether a domain name is under another one and how deep.
 *
 * \param name       The longer name to check.
 * \param bailiwick  The shorter name to check.
 *
 * \retval >=0 a subdomain nested this many labels.
 * \retval <0 not a subdomain (KNOT_EOUTOFZONE) or another error (KNOT_EINVAL).
 */
int knot_dname_in_bailiwick(const knot_dname_t *name, const knot_dname_t *bailiwick);

/*! @} */