/*
 * testcode/replay.c - store and use a replay of events for the DNS resolver.
 *
 * Copyright (c) 2007, 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 REGENTS 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
 * Store and use a replay of events for the DNS resolver.
 * Used to test known scenarios to get known outcomes.
 */

#include "config.h"
#include "util/log.h"
#include "util/net_help.h"
#include "testcode/replay.h"
#include "testcode/ldns-testpkts.h"

/** max length of lines in file */
#define MAX_LINE_LEN 10240

/** parse keyword in string. 
 * @param line: if found, the line is advanced to after the keyword.
 * @param keyword: string.
 * @return: true if found, false if not. 
 */
static int 
parse_keyword(char** line, char* keyword)
{
	size_t len = (size_t)strlen(keyword);
	if(strncmp(*line, keyword, len) == 0) {
		*line += len;
		return 1;
	}
	return 0;
}

/** delete moment */
static void
replay_moment_delete(struct replay_moment* mom)
{
	if(!mom)
		return;
	if(mom->match) {
		delete_entry(mom->match);
	}
	free(mom);
}

/** delete range */
static void
replay_range_delete(struct replay_range* rng)
{
	if(!rng)
		return;
	delete_entry(rng->match);
	free(rng);
}

/** strip whitespace from end of string */
static void
strip_end_white(char* p)
{
	size_t i;
	for(i = strlen(p); i > 0; i--) {
		if(isspace((int)p[i-1]))
			p[i-1] = 0;
		else return;
	}
}

/** 
 * Read a range from file. 
 * @param remain: Rest of line (after RANGE keyword).
 * @param in: file to read from.
 * @param name: name to print in errors.
 * @param lineno: incremented as lines are read.
 * @param line: line buffer.
 * @param ttl: for readentry
 * @param or: for readentry
 * @param prev: for readentry
 * @return: range object to add to list, or NULL on error.
 */
static struct replay_range*
replay_range_read(char* remain, FILE* in, const char* name, int* lineno, 
	char* line, uint32_t* ttl, ldns_rdf** or, ldns_rdf** prev)
{
	struct replay_range* rng = (struct replay_range*)malloc(
		sizeof(struct replay_range));
	off_t pos;
	char *parse;
	struct entry* entry, *last = NULL;
	if(!rng)
		return NULL;
	memset(rng, 0, sizeof(*rng));
	/* read time range */
	if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
		log_err("Could not read time range: %s", line);
		free(rng);
		return NULL;
	}
	/* read entries */
	pos = ftello(in);
	while(fgets(line, MAX_LINE_LEN-1, in)) {
		(*lineno)++;
		parse = line;
		while(isspace((int)*parse))
			parse++;
		if(!*parse || *parse == ';') {
			pos = ftello(in);
			continue;
		}
		if(parse_keyword(&parse, "ADDRESS")) {
			while(isspace((int)*parse))
				parse++;
			strip_end_white(parse);
			if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) {
				log_err("Line %d: could not read ADDRESS: %s", 
					*lineno, parse);
				free(rng);
				return NULL;
			}
			pos = ftello(in);
			continue;
		}
		if(parse_keyword(&parse, "RANGE_END")) {
			return rng;
		}
		/* set position before line; read entry */
		(*lineno)--;
		fseeko(in, pos, SEEK_SET);
		entry = read_entry(in, name, lineno, ttl, or, prev);
		if(!entry)
			fatal_exit("%d: bad entry", *lineno);
		entry->next = NULL;
		if(last)
			last->next = entry;
		else	rng->match = entry;
		last = entry;

		pos = ftello(in);
	}
	replay_range_delete(rng);
	return NULL;
}

/** 
 * Read a replay moment 'STEP' from file. 
 * @param remain: Rest of line (after STEP keyword).
 * @param in: file to read from.
 * @param name: name to print in errors.
 * @param lineno: incremented as lines are read.
 * @param ttl: for readentry
 * @param or: for readentry
 * @param prev: for readentry
 * @return: range object to add to list, or NULL on error.
 */
static struct replay_moment*
replay_moment_read(char* remain, FILE* in, const char* name, int* lineno, 
	uint32_t* ttl, ldns_rdf** or, ldns_rdf** prev)
{
	struct replay_moment* mom = (struct replay_moment*)malloc(
		sizeof(struct replay_moment));
	int skip = 0;
	int readentry = 0;
	if(!mom)
		return NULL;
	memset(mom, 0, sizeof(*mom));
	if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
		log_err("%d: cannot read number: %s", *lineno, remain);
		free(mom);
		return NULL;
	}
	remain += skip;
	while(isspace((int)*remain))
		remain++;
	if(parse_keyword(&remain, "NOTHING")) {
		mom->evt_type = repevt_nothing;
	} else if(parse_keyword(&remain, "QUERY")) {
		mom->evt_type = repevt_front_query;
		readentry = 1;
		if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen))
			fatal_exit("internal error");
	} else if(parse_keyword(&remain, "CHECK_ANSWER")) {
		mom->evt_type = repevt_front_reply;
		readentry = 1;
	} else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
		mom->evt_type = repevt_back_query;
		readentry = 1;
	} else if(parse_keyword(&remain, "REPLY")) {
		mom->evt_type = repevt_back_reply;
		readentry = 1;
	} else if(parse_keyword(&remain, "TIMEOUT")) {
		mom->evt_type = repevt_timeout;
	} else if(parse_keyword(&remain, "ERROR")) {
		mom->evt_type = repevt_error;
	} else {
		log_err("%d: unknown event type %s", *lineno, remain);
		free(mom);
		return NULL;
	}
	while(isspace((int)*remain))
		remain++;
	if(parse_keyword(&remain, "ADDRESS")) {
		while(isspace((int)*remain))
			remain++;
		if(strlen(remain) > 0) /* remove \n */
			remain[strlen(remain)-1] = 0;
		printf("remain '%s'\n", remain);
		if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) {
			log_err("line %d: could not parse ADDRESS: %s", 
				*lineno, remain);
			free(mom);
			return NULL;
		}
	} 

	if(readentry) {
		mom->match = read_entry(in, name, lineno, ttl, or, prev);
		if(!mom->match) {
			free(mom);
			return NULL;
		}
	}

	return mom;
}

/** makes scenario with title on rest of line */
static struct replay_scenario*
make_scenario(char* line)
{
	struct replay_scenario* scen;
	while(isspace((int)*line))
		line++;
	if(!*line) {
		log_err("scenario: no title given");
		return NULL;
	}
	scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
	if(!scen)
		return NULL;
	memset(scen, 0, sizeof(*scen));
	scen->title = strdup(line);
	if(!scen->title) {
		free(scen);
		return NULL;
	}
	return scen;
}

struct replay_scenario* 
replay_scenario_read(FILE* in, const char* name, int* lineno)
{
	char line[MAX_LINE_LEN];
	char *parse;
	struct replay_scenario* scen = NULL;
	uint32_t ttl = 3600;
	ldns_rdf* or = NULL;
	ldns_rdf* prev = NULL;
	line[MAX_LINE_LEN-1]=0;

	while(fgets(line, MAX_LINE_LEN-1, in)) {
		parse=line;
		(*lineno)++;
		while(isspace((int)*parse))
			parse++;
		if(!*parse) 
			continue; /* empty line */
		if(parse_keyword(&parse, ";"))
			continue; /* comment */
		if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
			scen = make_scenario(parse);
			if(!scen)
				fatal_exit("%d: could not make scen", *lineno);
			continue;
		} 
		if(!scen)
			fatal_exit("%d: expected SCENARIO", *lineno);
		if(parse_keyword(&parse, "RANGE_BEGIN")) {
			struct replay_range* newr = replay_range_read(parse, 
				in, name, lineno, line, &ttl, &or, &prev);
			if(!newr)
				fatal_exit("%d: bad range", *lineno);
			newr->next_range = scen->range_list;
			scen->range_list = newr;
		} else if(parse_keyword(&parse, "STEP")) {
			struct replay_moment* mom = replay_moment_read(parse, 
				in, name, lineno, &ttl, &or, &prev);
			if(!mom)
				fatal_exit("%d: bad moment", *lineno);
			if(scen->mom_last && 
				scen->mom_last->time_step >= mom->time_step)
				fatal_exit("%d: time goes backwards", *lineno);
			if(scen->mom_last)
				scen->mom_last->mom_next = mom;
			else	scen->mom_first = mom;
			scen->mom_last = mom;
		} else if(parse_keyword(&parse, "SCENARIO_END")) {
			struct replay_moment *p = scen->mom_first;
			int num = 0;
			while(p) {
				num++;
				p = p->mom_next;
			}
			log_info("Scenario has %d steps", num);
			ldns_rdf_deep_free(or);
			ldns_rdf_deep_free(prev);
			return scen;
		}
	}
	ldns_rdf_deep_free(or);
	ldns_rdf_deep_free(prev);
	replay_scenario_delete(scen);
	return NULL;
}

void 
replay_scenario_delete(struct replay_scenario* scen)
{
	struct replay_moment* mom, *momn;
	struct replay_range* rng, *rngn;
	if(!scen)
		return;
	if(scen->title)
		free(scen->title);
	mom = scen->mom_first;
	while(mom) {
		momn = mom->mom_next;
		replay_moment_delete(mom);
		mom = momn;
	}
	rng = scen->range_list;
	while(rng) {
		rngn = rng->next_range;
		replay_range_delete(rng);
		rng = rngn;
	}
	free(scen);
}
