/*
 * testcode/asynclook.c - debug program perform async libunbound queries.
 *
 * Copyright (c) 2008, NLnet Labs. All rights reserved.
 *
 * This software is open source.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the NLNET LABS nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * \file
 *
 * This program shows the results from several background lookups,
 * while printing time in the foreground.
 */

#include "config.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include "libunbound/unbound.h"
#include "libunbound/context.h"
#include "util/locks.h"
#include "util/log.h"
#include "sldns/rrdef.h"
#ifdef UNBOUND_ALLOC_LITE
#undef malloc
#undef calloc
#undef realloc
#undef free
#undef strdup
#endif
#ifdef HAVE_SSL
#ifdef HAVE_OPENSSL_SSL_H
#include <openssl/ssl.h>
#endif
#ifdef HAVE_OPENSSL_ERR_H
#include <openssl/err.h>
#endif
#endif /* HAVE_SSL */


/** keeping track of the async ids */
struct track_id {
	/** the id to pass to libunbound to cancel */
	int id;
	/** true if cancelled */
	int cancel;
	/** a lock on this structure for thread safety */
	lock_basic_type lock;
};

/**
 * result list for the lookups
 */
struct lookinfo {
	/** name to look up */
	char* name;
	/** tracking number that can be used to cancel the query */
	int async_id;
	/** error code from libunbound */
	int err;
	/** result from lookup */
	struct ub_result* result;
};

/** global variable to see how many queries we have left */
static int num_wait = 0;

/** usage information for asynclook */
static void usage(char* argv[])
{
	printf("usage: %s [options] name ...\n", argv[0]);
	printf("names are looked up at the same time, asynchronously.\n");
	printf("	-b : use blocking requests\n");
	printf("	-c : cancel the requests\n");
	printf("	-d : enable debug output\n");
	printf("	-f addr : use addr, forward to that server\n");
	printf("	-h : this help message\n");
	printf("	-H fname : read hosts from fname\n");
	printf("	-r fname : read resolv.conf from fname\n");
	printf("	-t : use a resolver thread instead of forking a process\n");
	printf("	-x : perform extended threaded test\n");
	exit(1);
}

/** print result from lookup nicely */
static void
print_result(struct lookinfo* info)
{
	char buf[100];
	if(info->err) /* error (from libunbound) */
		printf("%s: error %s\n", info->name,
			ub_strerror(info->err));
	else if(!info->result)
		printf("%s: cancelled\n", info->name);
	else if(info->result->havedata)
		printf("%s: %s\n", info->name,
			inet_ntop(AF_INET, info->result->data[0],
			buf, (socklen_t)sizeof(buf)));
	else {
		/* there is no data, why that? */
		if(info->result->rcode == 0 /*noerror*/ ||
			info->result->nxdomain)
			printf("%s: no data %s\n", info->name,
			info->result->nxdomain?"(no such host)":
			"(no IP4 address)");
		else	/* some error (from the server) */
			printf("%s: DNS error %d\n", info->name,
				info->result->rcode);
	}
}

/** this is a function of type ub_callback_t */
static void 
lookup_is_done(void* mydata, int err, struct ub_result* result)
{
	/* cast mydata back to the correct type */
	struct lookinfo* info = (struct lookinfo*)mydata;
	fprintf(stderr, "name %s resolved\n", info->name);
	info->err = err;
	info->result = result;
	/* one less to wait for */
	num_wait--;
}

/** check error, if bad, exit with error message */
static void 
checkerr(const char* desc, int err)
{
	if(err != 0) {
		printf("%s error: %s\n", desc, ub_strerror(err));
		exit(1);
	}
}

#ifdef THREADS_DISABLED
/** only one process can communicate with async worker */
#define NUMTHR 1
#else /* have threads */
/** number of threads to make in extended test */
#define NUMTHR 10
#endif

/** struct for extended thread info */
struct ext_thr_info {
	/** thread num for debug */
	int thread_num;
	/** thread id */
	ub_thread_type tid;
	/** context */
	struct ub_ctx* ctx;
	/** size of array to query */
	int argc;
	/** array of names to query */
	char** argv;
	/** number of queries to do */
	int numq;
};

/** if true, we are testing against 'localhost' and extra checking is done */
static int q_is_localhost = 0;

/** check result structure for the 'correct' answer */
static void
ext_check_result(const char* desc, int err, struct ub_result* result)
{
	checkerr(desc, err);
	if(result == NULL) {
		printf("%s: error result is NULL.\n", desc);
		exit(1);
	}
	if(q_is_localhost) {
		if(strcmp(result->qname, "localhost") != 0) {
			printf("%s: error result has wrong qname.\n", desc);
			exit(1);
		}
		if(result->qtype != LDNS_RR_TYPE_A) {
			printf("%s: error result has wrong qtype.\n", desc);
			exit(1);
		}
		if(result->qclass != LDNS_RR_CLASS_IN) {
			printf("%s: error result has wrong qclass.\n", desc);
			exit(1);
		}
		if(result->data == NULL) {
			printf("%s: error result->data is NULL.\n", desc);
			exit(1);
		}
		if(result->len == NULL) {
			printf("%s: error result->len is NULL.\n", desc);
			exit(1);
		}
		if(result->rcode != 0) {
			printf("%s: error result->rcode is set.\n", desc);
			exit(1);
		}
		if(result->havedata == 0) {
			printf("%s: error result->havedata is unset.\n", desc);
			exit(1);
		}
		if(result->nxdomain != 0) {
			printf("%s: error result->nxdomain is set.\n", desc);
			exit(1);
		}
		if(result->secure || result->bogus) {
			printf("%s: error result->secure or bogus is set.\n", 
				desc);
			exit(1);
		}
		if(result->data[0] == NULL) {
			printf("%s: error result->data[0] is NULL.\n", desc);
			exit(1);
		}
		if(result->len[0] != 4) {
			printf("%s: error result->len[0] is wrong.\n", desc);
			exit(1);
		}
		if(result->len[1] != 0 || result->data[1] != NULL) {
			printf("%s: error result->data[1] or len[1] is "
				"wrong.\n", desc);
			exit(1);
		}
		if(result->answer_packet == NULL) {
			printf("%s: error result->answer_packet is NULL.\n", 
				desc);
			exit(1);
		}
		if(result->answer_len != 54) {
			printf("%s: error result->answer_len is wrong.\n", 
				desc);
			exit(1);
		}
	}
}

/** extended bg result callback, this function is ub_callback_t */
static void 
ext_callback(void* mydata, int err, struct ub_result* result)
{
	struct track_id* my_id = (struct track_id*)mydata;
	int doprint = 0;
	if(my_id) {
		/* I have an id, make sure we are not cancelled */
		lock_basic_lock(&my_id->lock);
		if(doprint) 
			printf("cb %d: ", my_id->id);
		if(my_id->cancel) {
			printf("error: query id=%d returned, but was cancelled\n",
				my_id->id);
			abort();
			exit(1);
		}
		lock_basic_unlock(&my_id->lock);
	}
	ext_check_result("ext_callback", err, result);
	log_assert(result);
	if(doprint) {
		struct lookinfo pi;
		pi.name = result?result->qname:"noname";
		pi.result = result;
		pi.err = 0;
		print_result(&pi);
	}
	ub_resolve_free(result);
}

/** extended thread worker */
static void*
ext_thread(void* arg)
{
	struct ext_thr_info* inf = (struct ext_thr_info*)arg;
	int i, r;
	struct ub_result* result;
	struct track_id* async_ids = NULL;
	log_thread_set(&inf->thread_num);
	if(inf->thread_num > NUMTHR*2/3) {
		async_ids = (struct track_id*)calloc((size_t)inf->numq, sizeof(struct track_id));
		if(!async_ids) {
			printf("out of memory\n");
			exit(1);
		}
		for(i=0; i<inf->numq; i++) {
			lock_basic_init(&async_ids[i].lock);
		}
	}
	for(i=0; i<inf->numq; i++) {
		if(async_ids) {
			r = ub_resolve_async(inf->ctx, 
				inf->argv[i%inf->argc], LDNS_RR_TYPE_A, 
				LDNS_RR_CLASS_IN, &async_ids[i], ext_callback, 
				&async_ids[i].id);
			checkerr("ub_resolve_async", r);
			if(i > 100) {
				lock_basic_lock(&async_ids[i-100].lock);
				r = ub_cancel(inf->ctx, async_ids[i-100].id);
				if(r != UB_NOID)
					async_ids[i-100].cancel=1;
				lock_basic_unlock(&async_ids[i-100].lock);
				if(r != UB_NOID) 
					checkerr("ub_cancel", r);
			}
		} else if(inf->thread_num > NUMTHR/2) {
			/* async */
			r = ub_resolve_async(inf->ctx, 
				inf->argv[i%inf->argc], LDNS_RR_TYPE_A, 
				LDNS_RR_CLASS_IN, NULL, ext_callback, NULL);
			checkerr("ub_resolve_async", r);
		} else  {
			/* blocking */
			r = ub_resolve(inf->ctx, inf->argv[i%inf->argc], 
				LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &result);
			ext_check_result("ub_resolve", r, result);
			ub_resolve_free(result);
		}
	}
	if(inf->thread_num > NUMTHR/2) {
		r = ub_wait(inf->ctx);
		checkerr("ub_ctx_wait", r);
	}
	/* if these locks are destroyed, or if the async_ids is freed, then
	   a use-after-free happens in another thread.
	   The allocation is only part of this test, though. */
	/*
	if(async_ids) {
		for(i=0; i<inf->numq; i++) {
			lock_basic_destroy(&async_ids[i].lock);
		}
	}
	free(async_ids);
	*/
	
	return NULL;
}

/** perform extended threaded test */
static int
ext_test(struct ub_ctx* ctx, int argc, char** argv)
{
	struct ext_thr_info inf[NUMTHR];
	int i;
	if(argc == 1 && strcmp(argv[0], "localhost") == 0)
		q_is_localhost = 1;
	printf("extended test start (%d threads)\n", NUMTHR);
	for(i=0; i<NUMTHR; i++) {
		/* 0 = this, 1 = library bg worker */
		inf[i].thread_num = i+2;
		inf[i].ctx = ctx;
		inf[i].argc = argc;
		inf[i].argv = argv;
		inf[i].numq = 100;
		ub_thread_create(&inf[i].tid, ext_thread, &inf[i]);
	}
	/* the work happens here */
	for(i=0; i<NUMTHR; i++) {
		ub_thread_join(inf[i].tid);
	}
	printf("extended test end\n");
	ub_ctx_delete(ctx);
	checklock_stop();
	return 0;
}

/** getopt global, in case header files fail to declare it. */
extern int optind;
/** getopt global, in case header files fail to declare it. */
extern char* optarg;

/** main program for asynclook */
int main(int argc, char** argv) 
{
	int c;
	struct ub_ctx* ctx;
	struct lookinfo* lookups;
	int i, r, cancel=0, blocking=0, ext=0;

	/* init log now because solaris thr_key_create() is not threadsafe */
	log_init(0,0,0);
	/* lock debug start (if any) */
	checklock_start();

	/* create context */
	ctx = ub_ctx_create();
	if(!ctx) {
		printf("could not create context, %s\n", strerror(errno));
		return 1;
	}

	/* command line options */
	if(argc == 1) {
		usage(argv);
	}
	while( (c=getopt(argc, argv, "bcdf:hH:r:tx")) != -1) {
		switch(c) {
			case 'd':
				r = ub_ctx_debuglevel(ctx, 3);
				checkerr("ub_ctx_debuglevel", r);
				break;
			case 't':
				r = ub_ctx_async(ctx, 1);
				checkerr("ub_ctx_async", r);
				break;
			case 'c':
				cancel = 1;
				break;
			case 'b':
				blocking = 1;
				break;
			case 'r':
				r = ub_ctx_resolvconf(ctx, optarg);
				if(r != 0) {
					printf("ub_ctx_resolvconf "
						"error: %s : %s\n",
						ub_strerror(r), 
						strerror(errno));
					return 1;
				}
				break;
			case 'H':
				r = ub_ctx_hosts(ctx, optarg);
				if(r != 0) {
					printf("ub_ctx_hosts "
						"error: %s : %s\n",
						ub_strerror(r), 
						strerror(errno));
					return 1;
				}
				break;
			case 'f':
				r = ub_ctx_set_fwd(ctx, optarg);
				checkerr("ub_ctx_set_fwd", r);
				break;
			case 'x':
				ext = 1;
				break;
			case 'h':
			case '?':
			default:
				usage(argv);
		}
	}
	argc -= optind;
	argv += optind;

#ifdef HAVE_SSL
#ifdef HAVE_ERR_LOAD_CRYPTO_STRINGS
	ERR_load_crypto_strings();
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
	ERR_load_SSL_strings();
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_CRYPTO)
	OpenSSL_add_all_algorithms();
#else
	OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS
		| OPENSSL_INIT_ADD_ALL_DIGESTS
		| OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000 || !defined(HAVE_OPENSSL_INIT_SSL)
	(void)SSL_library_init();
#else
	(void)OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
#endif
#endif /* HAVE_SSL */

	if(ext)
		return ext_test(ctx, argc, argv);

	/* allocate array for results. */
	lookups = (struct lookinfo*)calloc((size_t)argc, 
		sizeof(struct lookinfo));
	if(!lookups) {
		printf("out of memory\n");
		return 1;
	}

	/* perform asynchronous calls */
	num_wait = argc;
	for(i=0; i<argc; i++) {
		lookups[i].name = argv[i];
		if(blocking) {
			fprintf(stderr, "lookup %s\n", argv[i]);
			r = ub_resolve(ctx, argv[i], LDNS_RR_TYPE_A,
				LDNS_RR_CLASS_IN, &lookups[i].result);
			checkerr("ub_resolve", r);
		} else {
			fprintf(stderr, "start async lookup %s\n", argv[i]);
			r = ub_resolve_async(ctx, argv[i], LDNS_RR_TYPE_A,
				LDNS_RR_CLASS_IN, &lookups[i], &lookup_is_done, 
				&lookups[i].async_id);
			checkerr("ub_resolve_async", r);
		}
	}
	if(blocking)
		num_wait = 0;
	else if(cancel) {
		for(i=0; i<argc; i++) {
			fprintf(stderr, "cancel %s\n", argv[i]);
			r = ub_cancel(ctx, lookups[i].async_id);
			if(r != UB_NOID) 
				checkerr("ub_cancel", r);
		}
		num_wait = 0;
	}

	/* wait while the hostnames are looked up. Do something useful here */
	if(num_wait > 0)
	    for(i=0; i<1000; i++) {
		usleep(100000);
		fprintf(stderr, "%g seconds passed\n", 0.1*(double)i);
		r = ub_process(ctx);
		checkerr("ub_process", r);
		if(num_wait == 0)
			break;
	}
	if(i>=999) {
		printf("timed out\n");
		return 0;
	}
	printf("lookup complete\n");

	/* print lookup results */
	for(i=0; i<argc; i++) {
		print_result(&lookups[i]);
		ub_resolve_free(lookups[i].result);
	}

	ub_ctx_delete(ctx);
	free(lookups);
	checklock_stop();
	return 0;
}
