/*
 * dname.h -- Domain name handling.
 *
 * Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
 *
 * See LICENSE for the license.
 *
 */

#ifndef _DNAME_H_
#define _DNAME_H_

#include <assert.h>
#include <stdio.h>

#include "buffer.h"
#include "region-allocator.h"

#if defined(NAMEDB_UPPERCASE) || defined(USE_NAMEDB_UPPERCASE)
#define DNAME_NORMALIZE        toupper
#else
#define DNAME_NORMALIZE        tolower
#endif


/*
 * Domain names stored in memory add some additional information to be
 * able to quickly index and compare by label.
 */
typedef struct dname dname_type;
struct dname
{
	/*
	 * The size (in bytes) of the domain name in wire format.
	 */
	uint8_t name_size;

	/*
	 * The number of labels in this domain name (including the
	 * root label).
	 */
	uint8_t label_count;

	/*
	  uint8_t label_offsets[label_count];
	  uint8_t name[name_size];
	*/
};


/*
 * Construct a new domain name based on NAME in wire format.  NAME
 * cannot contain compression pointers.
 *
 * Pre: NAME != NULL.
 */
const dname_type *dname_make(region_type *region, const uint8_t *name,
			     int normalize);

/*
 * Construct a new domain name based on wire format dname stored at
 * PACKET's current position.  Compression pointers are followed.  The
 * PACKET's current position is changed to the end of the wire format
 * dname or set to just after the first compression pointer.
 */
const dname_type *dname_make_from_packet(region_type *region,
					 buffer_type *packet,
					 int allow_pointers,
					 int normalize);

/*
 * parse wireformat from packet (following pointers) into the
 * given buffer. Returns length in buffer or 0 on error.
 * buffer must be MAXDOMAINLEN+1 long.
 */
int dname_make_wire_from_packet(uint8_t *buf,
				buffer_type *packet,
				int allow_pointers);

/*
 * Construct a new domain name based on the ASCII representation NAME.
 * If ORIGIN is not NULL and NAME is not terminated by a "." the
 * ORIGIN is appended to the result.  NAME can contain escape
 * sequences.
 *
 * Returns NULL on failure.  Otherwise a newly allocated domain name
 * is returned.
 *
 * Pre: name != NULL.
 */
const dname_type *dname_parse(region_type *region, const char *name);

/*
 * parse ascii string to wireformat domain name (without compression ptrs)
 * returns 0 on failure, the length of the wireformat on success.
 * the result is stored in the wirefmt which must be at least MAXDOMAINLEN
 * in size. On failure, the wirefmt can be altered.
 */
int dname_parse_wire(uint8_t* wirefmt, const char* name);

/*
 * Return NULL if DNAME is NULL or a copy of DNAME otherwise.
 */
const dname_type *dname_copy(region_type *region, const dname_type *dname);


/*
 * Copy the most significant LABEL_COUNT labels from dname.
 */
const dname_type *dname_partial_copy(region_type *region,
				     const dname_type *dname,
				     uint8_t label_count);


/*
 * The origin of DNAME.
 */
const dname_type *dname_origin(region_type *region, const dname_type *dname);

/*
 * Return true if LEFT is a subdomain of RIGHT.
 */
int dname_is_subdomain(const dname_type *left, const dname_type *right);


/*
 * Offsets into NAME for each label starting with the most
 * significant label (the root label, followed by the TLD,
 * etc).
 */
static inline const uint8_t *
dname_label_offsets(const dname_type *dname)
{
	return (const uint8_t *) ((const char *) dname + sizeof(dname_type));
}


/*
 * The actual name in wire format (a sequence of label, each
 * prefixed by a length byte, terminated by a zero length
 * label).
 */
static inline const uint8_t *
dname_name(const dname_type *dname)
{
	return (const uint8_t *) ((const char *) dname
				  + sizeof(dname_type)
				  + dname->label_count * sizeof(uint8_t));
}


/*
 * Return the label for DNAME specified by LABEL_INDEX.  The first
 * label (LABEL_INDEX == 0) is the root label, the next label is the
 * TLD, etc.
 *
 * Pre: dname != NULL && label_index < dname->label_count.
 */
static inline const uint8_t *
dname_label(const dname_type *dname, uint8_t label)
{
	uint8_t label_index;

	assert(dname != NULL);
	assert(label < dname->label_count);

	label_index = dname_label_offsets(dname)[label];
	assert(label_index < dname->name_size);

	return dname_name(dname) + label_index;
}


/*
 * Compare two domain names.  The comparison defines a lexicographical
 * ordering based on the domain name's labels, starting with the most
 * significant label.
 *
 * Return < 0 if LEFT < RIGHT, 0 if LEFT == RIGHT, and > 0 if LEFT >
 * RIGHT.  The comparison is case sensitive.
 *
 * Pre: left != NULL && right != NULL
 */
int dname_compare(const dname_type *left, const dname_type *right);


/*
 * Compare two labels.  The comparison defines a lexicographical
 * ordering based on the characters in the labels.
 *
 * Return < 0 if LEFT < RIGHT, 0 if LEFT == RIGHT, and > 0 if LEFT >
 * RIGHT.  The comparison is case sensitive.
 *
 * Pre: left != NULL && right != NULL
 *      label_is_normal(left) && label_is_normal(right)
 */
int label_compare(const uint8_t *left, const uint8_t *right);


/*
 * Returns the number of labels that match in LEFT and RIGHT, starting
 * with the most significant label.  Because the root label always
 * matches, the result will always be >= 1.
 *
 * Pre: left != NULL && right != NULL
 */
uint8_t dname_label_match_count(const dname_type *left,
				const dname_type *right);


/*
 * The total size (in bytes) allocated to store DNAME.
 *
 * Pre: dname != NULL
 */
static inline size_t
dname_total_size(const dname_type *dname)
{
	return (sizeof(dname_type)
		+ ((((size_t)dname->label_count) + ((size_t)dname->name_size))
		   * sizeof(uint8_t)));
}


/*
 * Is LABEL a normal LABEL (not a pointer or reserved)?
 *
 * Pre: label != NULL;
 */
static inline int
label_is_normal(const uint8_t *label)
{
	assert(label);
	return (label[0] & 0xc0) == 0;
}


/*
 * Is LABEL a pointer?
 *
 * Pre: label != NULL;
 */
static inline int
label_is_pointer(const uint8_t *label)
{
	assert(label);
	return (label[0] & 0xc0) == 0xc0;
}


/*
 * LABEL's pointer location.
 *
 * Pre: label != NULL && label_is_pointer(label)
 */
static inline uint16_t
label_pointer_location(const uint8_t *label)
{
	assert(label);
	assert(label_is_pointer(label));
	return ((uint16_t) (label[0] & ~0xc0) << 8) | (uint16_t) label[1];
}


/*
 * Length of LABEL.
 *
 * Pre: label != NULL && label_is_normal(label)
 */
static inline uint8_t
label_length(const uint8_t *label)
{
	assert(label);
	assert(label_is_normal(label));
	return label[0];
}


/*
 * The data of LABEL.
 *
 * Pre: label != NULL && label_is_normal(label)
 */
static inline const uint8_t *
label_data(const uint8_t *label)
{
	assert(label);
	assert(label_is_normal(label));
	return label + 1;
}


/*
 * Is LABEL the root label?
 *
 * Pre: label != NULL
 */
static inline int
label_is_root(const uint8_t *label)
{
	assert(label);
	return label[0] == 0;
}


/*
 * Is LABEL the wildcard label?
 *
 * Pre: label != NULL
 */
static inline int
label_is_wildcard(const uint8_t *label)
{
	assert(label);
	return label[0] == 1 && label[1] == '*';
}


/*
 * The next label of LABEL.
 *
 * Pre: label != NULL
 *      label_is_normal(label)
 *      !label_is_root(label)
 */
static inline const uint8_t *
label_next(const uint8_t *label)
{
	assert(label);
	assert(label_is_normal(label));
	assert(!label_is_root(label));
	return label + label_length(label) + 1;
}


/*
 * Convert DNAME to its string representation.  The result points to a
 * static buffer that is overwritten the next time this function is
 * invoked.
 *
 * If ORIGIN is provided and DNAME is a subdomain of ORIGIN the dname
 * will be represented relative to ORIGIN.
 *
 * Pre: dname != NULL
 */
const char *dname_to_string(const dname_type *dname,
			    const dname_type *origin);


/*
 * Create a dname containing the single label specified by STR
 * followed by the root label.
 */
const dname_type *dname_make_from_label(region_type *region,
					const uint8_t *label,
					const size_t length);


/*
 * Concatenate two dnames.
 */
const dname_type *dname_concatenate(region_type *region,
				    const dname_type *left,
				    const dname_type *right);


/*
 * Perform DNAME substitution on a name, replace src with dest.
 * Name must be a subdomain of src. The returned name is a subdomain of dest.
 * Returns NULL if the result domain name is too long.
 */
const dname_type *dname_replace(region_type* region,
				const dname_type* name,
				const dname_type* src,
				const dname_type* dest);

/** Convert uncompressed wireformat dname to a string */
char* wiredname2str(const uint8_t* dname);
/** convert uncompressed label to string */
char* wirelabel2str(const uint8_t* label);
/** check if two uncompressed dnames of the same total length are equal */
int dname_equal_nocase(uint8_t* a, uint8_t* b, uint16_t len);

#endif /* _DNAME_H_ */
