/*
 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <stdlib.h>
#include <strings.h>
#include <mach/mach.h>
#include <mach/mach_error.h>

#include "dnsinfo_create.h"
#include "dnsinfo_private.h"
#include "shared_dns_info.h"


#define ROUNDUP(a, size) \
	(((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))


/*
 * to avoid extra calls to realloc() we want to pre-allocate the initial
 * resolver and configuration buffers of a sufficient size that they would
 * not normally need to be expanded.
 */
#define INITIAL_CONFIGURATION_BUF_SIZE  8192
#define INITIAL_RESOLVER_BUF_SIZE       1024


/*
 * DNS [configuration] buffer functions
 */


__private_extern__
dns_create_config_t
_dns_configuration_create()
{
	_dns_config_buf_t	*config;

	config = calloc(1, INITIAL_CONFIGURATION_BUF_SIZE);
//	config->n_attribute = 0;
//	config->n_padding = 0;
	return (dns_create_config_t)config;
}


static void
config_add_attribute(dns_create_config_t	*_config,
		     uint32_t			attribute_type,
		     uint32_t			attribute_length,
		     void			*attribute,
		     uint32_t			extra_padding)
{
	_dns_config_buf_t	*config	= (_dns_config_buf_t *)*_config;
	dns_attribute_t		*header;
	int			i;
	uint32_t		newLen;
	uint32_t		newSize;
	uint32_t		oldLen;
	uint32_t		rounded_length;

	// add space

	oldLen         = ntohl(config->n_attribute);
	rounded_length = ROUNDUP(attribute_length, sizeof(uint32_t));
	newLen         = sizeof(dns_attribute_t) + rounded_length;
	newSize = sizeof(_dns_config_buf_t) + oldLen + newLen;
	if (newSize > INITIAL_CONFIGURATION_BUF_SIZE) {
		config = realloc(config, newSize);
	}
	config->n_attribute = htonl(ntohl(config->n_attribute) + newLen);

	// increment additional padding that will be needed (later)
	config->n_padding = htonl(ntohl(config->n_padding) + extra_padding);

	// add attribute [header]

	header = (dns_attribute_t *)&config->attribute[oldLen];
	header->type   = htonl(attribute_type);
	header->length = htonl(newLen);

	// add attribute [data]

	bcopy(attribute, &header->attribute[0], attribute_length);
	for (i = attribute_length; i < rounded_length; i++) {
		header->attribute[i] = 0;
	}

	*_config = (dns_create_config_t)config;
	return;
}


__private_extern__
void
_dns_configuration_add_resolver(dns_create_config_t     *_config,
				dns_create_resolver_t	_resolver)
{
	_dns_config_buf_t	*config		= (_dns_config_buf_t *)*_config;
	uint32_t		padding		= 0;
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)_resolver;

	/*
	 * compute the amount of space that will be needed for
	 * pointers to the resolver, the nameservers, the search
	 * list, and the sortaddr list.
	 */
	padding += sizeof(dns_resolver_t *);
	if (resolver->resolver.n_nameserver != 0) {
		padding += ntohl(resolver->resolver.n_nameserver) * sizeof(struct sockaddr *);
	}
	if (resolver->resolver.n_search != 0) {
		padding += ntohl(resolver->resolver.n_search) * sizeof(char *);
	}
	if (resolver->resolver.n_sortaddr != 0) {
		padding += ntohl(resolver->resolver.n_sortaddr) * sizeof(dns_sortaddr_t *);
	}

	config->config.n_resolver = htonl(ntohl(config->config.n_resolver) + 1);

	config_add_attribute(_config,
			     CONFIG_ATTRIBUTE_RESOLVER,
			     sizeof(_dns_resolver_buf_t) + ntohl(resolver->n_attribute),
			     (void *)resolver,
			     padding);

	return;
}


__private_extern__
_Bool
_dns_configuration_store(dns_create_config_t *_config)
{
	dnsDataOut_t		dataRef	= NULL;
	mach_msg_type_number_t	dataLen	= 0;
	mach_port_t		server;
	kern_return_t		status;

	server = _dns_configuration_server_port();
	if (server == MACH_PORT_NULL) {
		return FALSE;
	}

	if (_config != NULL) {
		_dns_config_buf_t	*config	= (_dns_config_buf_t *)*_config;

		if (config != NULL) {
			dataRef = (dnsDataOut_t)config;
			dataLen = sizeof(_dns_config_buf_t) + ntohl(config->n_attribute);
		}
	}

	status = shared_dns_infoSet(server, dataRef, dataLen);
	(void)mach_port_deallocate(mach_task_self(), server);
	if (status != KERN_SUCCESS) {
		mach_error("shared_dns_infoSet():", status);
		return FALSE;
	}

	return TRUE;
}


__private_extern__
void
_dns_configuration_free(dns_create_config_t *_config)
{
	_dns_config_buf_t	*config	= (_dns_config_buf_t *)*_config;

	free(config);
	*_config = NULL;
	return;
}


/*
 * DNS resolver configuration functions
 */

__private_extern__
dns_create_resolver_t
_dns_resolver_create()
{
	_dns_resolver_buf_t	*buf;

	buf = calloc(1, INITIAL_RESOLVER_BUF_SIZE);
//	buf->n_attribute = 0;
	return (dns_create_resolver_t)buf;
}


static void
_dns_resolver_add_attribute(dns_create_resolver_t	*_resolver,
			    uint32_t			attribute_type,
			    uint32_t			attribute_length,
			    void			*attribute)
{
	dns_attribute_t		*header;
	int			i;
	uint32_t		newLen;
	uint32_t		newSize;
	uint32_t		oldLen;
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;
	uint32_t		rounded_length;

	// add space

	oldLen         = ntohl(resolver->n_attribute);
	rounded_length = ROUNDUP(attribute_length, sizeof(uint32_t));
	newLen         = sizeof(dns_attribute_t) + rounded_length;
	newSize = sizeof(_dns_resolver_buf_t) + oldLen + newLen;
	if (newSize > INITIAL_RESOLVER_BUF_SIZE) {
		resolver = realloc(resolver, newSize);
	}
	resolver->n_attribute = htonl(ntohl(resolver->n_attribute) + newLen);

	// add attribute [header]

	header = (dns_attribute_t *)&resolver->attribute[oldLen];
	header->type   = htonl(attribute_type);
	header->length = htonl(newLen);

	// add attribute [data]

	bcopy(attribute, &header->attribute[0], attribute_length);
	for (i = attribute_length; i < rounded_length; i++) {
		header->attribute[i] = 0;
	}

	*_resolver = (dns_create_resolver_t)resolver;
	return;
}


__private_extern__
void
_dns_resolver_set_domain(dns_create_resolver_t *_resolver, const char *domain)
{
	_dns_resolver_add_attribute(_resolver, RESOLVER_ATTRIBUTE_DOMAIN, strlen(domain) + 1, (void *)domain);
	return;
}


__private_extern__
void
_dns_resolver_add_nameserver(dns_create_resolver_t *_resolver, struct sockaddr *nameserver)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.n_nameserver = htonl(ntohl(resolver->resolver.n_nameserver) + 1);
	_dns_resolver_add_attribute(_resolver, RESOLVER_ATTRIBUTE_ADDRESS, nameserver->sa_len, (void *)nameserver);
	return;
}


__private_extern__
void
_dns_resolver_set_port(dns_create_resolver_t *_resolver, uint32_t port)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.port = htons(port);
	return;
}


__private_extern__
void
_dns_resolver_add_search(dns_create_resolver_t *_resolver, const char *search)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.n_search = htonl(ntohl(resolver->resolver.n_search) + 1);
	_dns_resolver_add_attribute(_resolver, RESOLVER_ATTRIBUTE_SEARCH, strlen(search) + 1, (void *)search);
	return;
}


__private_extern__
void
_dns_resolver_add_sortaddr(dns_create_resolver_t *_resolver, dns_sortaddr_t *sortaddr)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.n_sortaddr = htonl(ntohl(resolver->resolver.n_sortaddr) + 1);
	_dns_resolver_add_attribute(_resolver, RESOLVER_ATTRIBUTE_SORTADDR, sizeof(dns_sortaddr_t), (void *)sortaddr);
	return;
}


__private_extern__
void
_dns_resolver_set_options(dns_create_resolver_t *_resolver, const char *options)
{
	_dns_resolver_add_attribute(_resolver, RESOLVER_ATTRIBUTE_OPTIONS, strlen(options) + 1, (void *)options);
	return;
}


__private_extern__
void
_dns_resolver_set_timeout(dns_create_resolver_t *_resolver, uint32_t timeout)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.timeout = htonl(timeout);
	return;
}


__private_extern__
void
_dns_resolver_set_order(dns_create_resolver_t *_resolver, uint32_t order)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	resolver->resolver.search_order = htonl(order);
	return;
}


__private_extern__
void
_dns_resolver_free(dns_create_resolver_t *_resolver)
{
	_dns_resolver_buf_t	*resolver	= (_dns_resolver_buf_t *)*_resolver;

	free(resolver);
	*_resolver = NULL;
	return;
}
