replay.c revision 1.1.1.5
1/*
2 * testcode/replay.c - store and use a replay of events for the DNS resolver.
3 *
4 * Copyright (c) 2007, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36/**
37 * \file
38 * Store and use a replay of events for the DNS resolver.
39 * Used to test known scenarios to get known outcomes.
40 */
41
42#include "config.h"
43/* for strtod prototype */
44#include <math.h>
45#include <ctype.h>
46#include <time.h>
47#include "util/log.h"
48#include "util/net_help.h"
49#include "util/config_file.h"
50#include "testcode/replay.h"
51#include "testcode/testpkts.h"
52#include "testcode/fake_event.h"
53#include "sldns/str2wire.h"
54
55/** max length of lines in file */
56#define MAX_LINE_LEN 10240
57
58/**
59 * Expand a macro
60 * @param store: value storage
61 * @param runtime: replay runtime for other stuff.
62 * @param text: the macro text, after the ${, Updated to after the } when
63 * 	done (successfully).
64 * @return expanded text, malloced. NULL on failure.
65 */
66static char* macro_expand(rbtree_type* store,
67	struct replay_runtime* runtime, char** text);
68
69/** compare of time values */
70static int
71timeval_smaller(const struct timeval* x, const struct timeval* y)
72{
73#ifndef S_SPLINT_S
74	if(x->tv_sec < y->tv_sec)
75		return 1;
76	else if(x->tv_sec == y->tv_sec) {
77		if(x->tv_usec <= y->tv_usec)
78			return 1;
79		else	return 0;
80	}
81	else	return 0;
82#endif
83}
84
85/** parse keyword in string.
86 * @param line: if found, the line is advanced to after the keyword.
87 * @param keyword: string.
88 * @return: true if found, false if not.
89 */
90static int
91parse_keyword(char** line, const char* keyword)
92{
93	size_t len = (size_t)strlen(keyword);
94	if(strncmp(*line, keyword, len) == 0) {
95		*line += len;
96		return 1;
97	}
98	return 0;
99}
100
101/** delete moment */
102static void
103replay_moment_delete(struct replay_moment* mom)
104{
105	if(!mom)
106		return;
107	if(mom->match) {
108		delete_entry(mom->match);
109	}
110	free(mom->autotrust_id);
111	free(mom->string);
112	free(mom->variable);
113	config_delstrlist(mom->file_content);
114	free(mom);
115}
116
117/** delete range */
118static void
119replay_range_delete(struct replay_range* rng)
120{
121	if(!rng)
122		return;
123	delete_entry(rng->match);
124	free(rng);
125}
126
127void
128strip_end_white(char* p)
129{
130	size_t i;
131	for(i = strlen(p); i > 0; i--) {
132		if(isspace((unsigned char)p[i-1]))
133			p[i-1] = 0;
134		else return;
135	}
136}
137
138/**
139 * Read a range from file.
140 * @param remain: Rest of line (after RANGE keyword).
141 * @param in: file to read from.
142 * @param name: name to print in errors.
143 * @param pstate: read state structure with
144 * 	with lineno : incremented as lines are read.
145 * 	ttl, origin, prev for readentry.
146 * @param line: line buffer.
147 * @return: range object to add to list, or NULL on error.
148 */
149static struct replay_range*
150replay_range_read(char* remain, FILE* in, const char* name,
151	struct sldns_file_parse_state* pstate, char* line)
152{
153	struct replay_range* rng = (struct replay_range*)malloc(
154		sizeof(struct replay_range));
155	off_t pos;
156	char *parse;
157	struct entry* entry, *last = NULL;
158	if(!rng)
159		return NULL;
160	memset(rng, 0, sizeof(*rng));
161	/* read time range */
162	if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
163		log_err("Could not read time range: %s", line);
164		free(rng);
165		return NULL;
166	}
167	/* read entries */
168	pos = ftello(in);
169	while(fgets(line, MAX_LINE_LEN-1, in)) {
170		pstate->lineno++;
171		parse = line;
172		while(isspace((unsigned char)*parse))
173			parse++;
174		if(!*parse || *parse == ';') {
175			pos = ftello(in);
176			continue;
177		}
178		if(parse_keyword(&parse, "ADDRESS")) {
179			while(isspace((unsigned char)*parse))
180				parse++;
181			strip_end_white(parse);
182			if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen,
183				UNBOUND_DNS_PORT)) {
184				log_err("Line %d: could not read ADDRESS: %s",
185					pstate->lineno, parse);
186				free(rng);
187				return NULL;
188			}
189			pos = ftello(in);
190			continue;
191		}
192		if(parse_keyword(&parse, "RANGE_END")) {
193			return rng;
194		}
195		/* set position before line; read entry */
196		pstate->lineno--;
197		fseeko(in, pos, SEEK_SET);
198		entry = read_entry(in, name, pstate, 1);
199		if(!entry)
200			fatal_exit("%d: bad entry", pstate->lineno);
201		entry->next = NULL;
202		if(last)
203			last->next = entry;
204		else	rng->match = entry;
205		last = entry;
206
207		pos = ftello(in);
208	}
209	replay_range_delete(rng);
210	return NULL;
211}
212
213/** Read FILE match content */
214static void
215read_file_content(FILE* in, int* lineno, struct replay_moment* mom)
216{
217	char line[MAX_LINE_LEN];
218	char* remain = line;
219	struct config_strlist** last = &mom->file_content;
220	line[MAX_LINE_LEN-1]=0;
221	if(!fgets(line, MAX_LINE_LEN-1, in))
222		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
223	if(!parse_keyword(&remain, "FILE_BEGIN"))
224		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
225	while(fgets(line, MAX_LINE_LEN-1, in)) {
226		(*lineno)++;
227		if(strncmp(line, "FILE_END", 8) == 0) {
228			return;
229		}
230		strip_end_white(line);
231		if(!cfg_strlist_insert(last, strdup(line)))
232			fatal_exit("malloc failure");
233		last = &( (*last)->next );
234	}
235	fatal_exit("no FILE_END in input file");
236}
237
238/** read assign step info */
239static void
240read_assign_step(char* remain, struct replay_moment* mom)
241{
242	char buf[1024];
243	char eq;
244	int skip;
245	buf[sizeof(buf)-1]=0;
246	if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2)
247		fatal_exit("cannot parse assign: %s", remain);
248	mom->variable = strdup(buf);
249	if(eq != '=')
250		fatal_exit("no '=' in assign: %s", remain);
251	remain += skip;
252	strip_end_white(remain);
253	mom->string = strdup(remain);
254	if(!mom->variable || !mom->string)
255		fatal_exit("out of memory");
256}
257
258/**
259 * Read a replay moment 'STEP' from file.
260 * @param remain: Rest of line (after STEP keyword).
261 * @param in: file to read from.
262 * @param name: name to print in errors.
263 * @param pstate: with lineno, ttl, origin, prev for parse state.
264 * 	lineno is incremented.
265 * @return: range object to add to list, or NULL on error.
266 */
267static struct replay_moment*
268replay_moment_read(char* remain, FILE* in, const char* name,
269	struct sldns_file_parse_state* pstate)
270{
271	struct replay_moment* mom = (struct replay_moment*)malloc(
272		sizeof(struct replay_moment));
273	int skip = 0;
274	int readentry = 0;
275	if(!mom)
276		return NULL;
277	memset(mom, 0, sizeof(*mom));
278	if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
279		log_err("%d: cannot read number: %s", pstate->lineno, remain);
280		free(mom);
281		return NULL;
282	}
283	remain += skip;
284	while(isspace((unsigned char)*remain))
285		remain++;
286	if(parse_keyword(&remain, "NOTHING")) {
287		mom->evt_type = repevt_nothing;
288	} else if(parse_keyword(&remain, "QUERY")) {
289		mom->evt_type = repevt_front_query;
290		readentry = 1;
291		if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen,
292			UNBOUND_DNS_PORT))
293			fatal_exit("internal error");
294	} else if(parse_keyword(&remain, "CHECK_ANSWER")) {
295		mom->evt_type = repevt_front_reply;
296		readentry = 1;
297	} else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
298		mom->evt_type = repevt_back_query;
299		readentry = 1;
300	} else if(parse_keyword(&remain, "REPLY")) {
301		mom->evt_type = repevt_back_reply;
302		readentry = 1;
303	} else if(parse_keyword(&remain, "TIMEOUT")) {
304		mom->evt_type = repevt_timeout;
305	} else if(parse_keyword(&remain, "TIME_PASSES")) {
306		mom->evt_type = repevt_time_passes;
307		while(isspace((unsigned char)*remain))
308			remain++;
309		if(parse_keyword(&remain, "EVAL")) {
310			while(isspace((unsigned char)*remain))
311				remain++;
312			mom->string = strdup(remain);
313			if(!mom->string) fatal_exit("out of memory");
314			if(strlen(mom->string)>0)
315				mom->string[strlen(mom->string)-1]=0;
316			remain += strlen(mom->string);
317		}
318	} else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) {
319		mom->evt_type = repevt_autotrust_check;
320		while(isspace((unsigned char)*remain))
321			remain++;
322		strip_end_white(remain);
323		mom->autotrust_id = strdup(remain);
324		if(!mom->autotrust_id) fatal_exit("out of memory");
325		read_file_content(in, &pstate->lineno, mom);
326	} else if(parse_keyword(&remain, "CHECK_TEMPFILE")) {
327		mom->evt_type = repevt_tempfile_check;
328		while(isspace((unsigned char)*remain))
329			remain++;
330		strip_end_white(remain);
331		mom->autotrust_id = strdup(remain);
332		if(!mom->autotrust_id) fatal_exit("out of memory");
333		read_file_content(in, &pstate->lineno, mom);
334	} else if(parse_keyword(&remain, "ERROR")) {
335		mom->evt_type = repevt_error;
336	} else if(parse_keyword(&remain, "TRAFFIC")) {
337		mom->evt_type = repevt_traffic;
338	} else if(parse_keyword(&remain, "ASSIGN")) {
339		mom->evt_type = repevt_assign;
340		read_assign_step(remain, mom);
341	} else if(parse_keyword(&remain, "INFRA_RTT")) {
342		char *s, *m;
343		mom->evt_type = repevt_infra_rtt;
344		while(isspace((unsigned char)*remain))
345			remain++;
346		s = remain;
347		remain = strchr(s, ' ');
348		if(!remain) fatal_exit("expected three args for INFRA_RTT");
349		remain[0] = 0;
350		remain++;
351		while(isspace((unsigned char)*remain))
352			remain++;
353		m = strchr(remain, ' ');
354		if(!m) fatal_exit("expected three args for INFRA_RTT");
355		m[0] = 0;
356		m++;
357		while(isspace((unsigned char)*m))
358			m++;
359		if(!extstrtoaddr(s, &mom->addr, &mom->addrlen, UNBOUND_DNS_PORT))
360			fatal_exit("bad infra_rtt address %s", s);
361		strip_end_white(m);
362		mom->variable = strdup(remain);
363		mom->string = strdup(m);
364		if(!mom->string) fatal_exit("out of memory");
365		if(!mom->variable) fatal_exit("out of memory");
366	} else {
367		log_err("%d: unknown event type %s", pstate->lineno, remain);
368		free(mom);
369		return NULL;
370	}
371	while(isspace((unsigned char)*remain))
372		remain++;
373	if(parse_keyword(&remain, "ADDRESS")) {
374		while(isspace((unsigned char)*remain))
375			remain++;
376		strip_end_white(remain);
377		if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen,
378			UNBOUND_DNS_PORT)) {
379			log_err("line %d: could not parse ADDRESS: %s",
380				pstate->lineno, remain);
381			free(mom);
382			return NULL;
383		}
384	}
385	if(parse_keyword(&remain, "ELAPSE")) {
386		double sec;
387		errno = 0;
388		sec = strtod(remain, &remain);
389		if(sec == 0. && errno != 0) {
390			log_err("line %d: could not parse ELAPSE: %s (%s)",
391				pstate->lineno, remain, strerror(errno));
392			free(mom);
393			return NULL;
394		}
395#ifndef S_SPLINT_S
396		mom->elapse.tv_sec = (int)sec;
397		mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
398			*1000000. + 0.5);
399#endif
400	}
401
402	if(readentry) {
403		mom->match = read_entry(in, name, pstate, 1);
404		if(!mom->match) {
405			free(mom);
406			return NULL;
407		}
408	}
409
410	return mom;
411}
412
413/** makes scenario with title on rest of line */
414static struct replay_scenario*
415make_scenario(char* line)
416{
417	struct replay_scenario* scen;
418	while(isspace((unsigned char)*line))
419		line++;
420	if(!*line) {
421		log_err("scenario: no title given");
422		return NULL;
423	}
424	scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
425	if(!scen)
426		return NULL;
427	memset(scen, 0, sizeof(*scen));
428	scen->title = strdup(line);
429	if(!scen->title) {
430		free(scen);
431		return NULL;
432	}
433	return scen;
434}
435
436struct replay_scenario*
437replay_scenario_read(FILE* in, const char* name, int* lineno)
438{
439	char line[MAX_LINE_LEN];
440	char *parse;
441	struct replay_scenario* scen = NULL;
442	struct sldns_file_parse_state pstate;
443	line[MAX_LINE_LEN-1]=0;
444	memset(&pstate, 0, sizeof(pstate));
445	pstate.default_ttl = 3600;
446	pstate.lineno = *lineno;
447
448	while(fgets(line, MAX_LINE_LEN-1, in)) {
449		parse=line;
450		pstate.lineno++;
451		(*lineno)++;
452		while(isspace((unsigned char)*parse))
453			parse++;
454		if(!*parse)
455			continue; /* empty line */
456		if(parse_keyword(&parse, ";"))
457			continue; /* comment */
458		if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
459			if(scen)
460				fatal_exit("%d: double SCENARIO_BEGIN", *lineno);
461			scen = make_scenario(parse);
462			if(!scen)
463				fatal_exit("%d: could not make scen", *lineno);
464			continue;
465		}
466		if(!scen)
467			fatal_exit("%d: expected SCENARIO", *lineno);
468		if(parse_keyword(&parse, "RANGE_BEGIN")) {
469			struct replay_range* newr = replay_range_read(parse,
470				in, name, &pstate, line);
471			if(!newr)
472				fatal_exit("%d: bad range", pstate.lineno);
473			*lineno = pstate.lineno;
474			newr->next_range = scen->range_list;
475			scen->range_list = newr;
476		} else if(parse_keyword(&parse, "STEP")) {
477			struct replay_moment* mom = replay_moment_read(parse,
478				in, name, &pstate);
479			if(!mom)
480				fatal_exit("%d: bad moment", pstate.lineno);
481			*lineno = pstate.lineno;
482			if(scen->mom_last &&
483				scen->mom_last->time_step >= mom->time_step)
484				fatal_exit("%d: time goes backwards", *lineno);
485			if(scen->mom_last)
486				scen->mom_last->mom_next = mom;
487			else	scen->mom_first = mom;
488			scen->mom_last = mom;
489		} else if(parse_keyword(&parse, "SCENARIO_END")) {
490			struct replay_moment *p = scen->mom_first;
491			int num = 0;
492			while(p) {
493				num++;
494				p = p->mom_next;
495			}
496			log_info("Scenario has %d steps", num);
497			return scen;
498		}
499	}
500	log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
501	replay_scenario_delete(scen);
502	return NULL;
503}
504
505void
506replay_scenario_delete(struct replay_scenario* scen)
507{
508	struct replay_moment* mom, *momn;
509	struct replay_range* rng, *rngn;
510	if(!scen)
511		return;
512	free(scen->title);
513	mom = scen->mom_first;
514	while(mom) {
515		momn = mom->mom_next;
516		replay_moment_delete(mom);
517		mom = momn;
518	}
519	rng = scen->range_list;
520	while(rng) {
521		rngn = rng->next_range;
522		replay_range_delete(rng);
523		rng = rngn;
524	}
525	free(scen);
526}
527
528/** fetch oldest timer in list that is enabled */
529static struct fake_timer*
530first_timer(struct replay_runtime* runtime)
531{
532	struct fake_timer* p, *res = NULL;
533	for(p=runtime->timer_list; p; p=p->next) {
534		if(!p->enabled)
535			continue;
536		if(!res)
537			res = p;
538		else if(timeval_smaller(&p->tv, &res->tv))
539			res = p;
540	}
541	return res;
542}
543
544struct fake_timer*
545replay_get_oldest_timer(struct replay_runtime* runtime)
546{
547	struct fake_timer* t = first_timer(runtime);
548	if(t && timeval_smaller(&t->tv, &runtime->now_tv))
549		return t;
550	return NULL;
551}
552
553int
554replay_var_compare(const void* a, const void* b)
555{
556	struct replay_var* x = (struct replay_var*)a;
557	struct replay_var* y = (struct replay_var*)b;
558	return strcmp(x->name, y->name);
559}
560
561rbtree_type*
562macro_store_create(void)
563{
564	return rbtree_create(&replay_var_compare);
565}
566
567/** helper function to delete macro values */
568static void
569del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
570{
571	struct replay_var* v = (struct replay_var*)x;
572	free(v->name);
573	free(v->value);
574	free(v);
575}
576
577void
578macro_store_delete(rbtree_type* store)
579{
580	if(!store)
581		return;
582	traverse_postorder(store, del_macro, NULL);
583	free(store);
584}
585
586/** return length of macro */
587static size_t
588macro_length(char* text)
589{
590	/* we are after ${, looking for } */
591	int depth = 0;
592	size_t len = 0;
593	while(*text) {
594		len++;
595		if(*text == '}') {
596			if(depth == 0)
597				break;
598			depth--;
599		} else if(text[0] == '$' && text[1] == '{') {
600			depth++;
601		}
602		text++;
603	}
604	return len;
605}
606
607/** insert new stuff at start of buffer */
608static int
609do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
610{
611	char* save = strdup(after);
612	size_t len;
613	if(!save) return 0;
614	if(strlen(inserted) > remain) {
615		free(save);
616		return 0;
617	}
618	len = strlcpy(buf, inserted, remain);
619	buf += len;
620	remain -= len;
621	(void)strlcpy(buf, save, remain);
622	free(save);
623	return 1;
624}
625
626/** do macro recursion */
627static char*
628do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
629	char* at, size_t remain)
630{
631	char* after = at+2;
632	char* expand = macro_expand(store, runtime, &after);
633	if(!expand)
634		return NULL; /* expansion failed */
635	if(!do_buf_insert(at, remain, after, expand)) {
636		free(expand);
637		return NULL;
638	}
639	free(expand);
640	return at; /* and parse over the expanded text to see if again */
641}
642
643/** get var from store */
644static struct replay_var*
645macro_getvar(rbtree_type* store, char* name)
646{
647	struct replay_var k;
648	k.node.key = &k;
649	k.name = name;
650	return (struct replay_var*)rbtree_search(store, &k);
651}
652
653/** do macro variable */
654static char*
655do_macro_variable(rbtree_type* store, char* buf, size_t remain)
656{
657	struct replay_var* v;
658	char* at = buf+1;
659	char* name = at;
660	char sv;
661	if(at[0]==0)
662		return NULL; /* no variable name after $ */
663	while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
664		at++;
665	}
666	/* terminator, we are working in macro_expand() buffer */
667	sv = *at;
668	*at = 0;
669	v = macro_getvar(store, name);
670	*at = sv;
671
672	if(!v) {
673		log_err("variable is not defined: $%s", name);
674		return NULL; /* variable undefined is error for now */
675	}
676
677	/* insert the variable contents */
678	if(!do_buf_insert(buf, remain, at, v->value))
679		return NULL;
680	return buf; /* and expand the variable contents */
681}
682
683/** do ctime macro on argument */
684static char*
685do_macro_ctime(char* arg)
686{
687	char buf[32];
688	time_t tt = (time_t)atoi(arg);
689	if(tt == 0 && strcmp(arg, "0") != 0) {
690		log_err("macro ctime: expected number, not: %s", arg);
691		return NULL;
692	}
693	ctime_r(&tt, buf);
694#ifdef USE_WINSOCK
695	if(strlen(buf) > 10 && buf[7]==' ' && buf[8]=='0')
696		buf[8]=' '; /* fix error in windows ctime */
697#endif
698	strip_end_white(buf);
699	return strdup(buf);
700}
701
702/** perform arithmetic operator */
703static double
704perform_arith(double x, char op, double y, double* res)
705{
706	switch(op) {
707	case '+':
708		*res = x+y;
709		break;
710	case '-':
711		*res = x-y;
712		break;
713	case '/':
714		*res = x/y;
715		break;
716	case '*':
717		*res = x*y;
718		break;
719	default:
720		*res = 0;
721		return 0;
722	}
723
724	return 1;
725}
726
727/** do macro arithmetic on two numbers and operand */
728static char*
729do_macro_arith(char* orig, size_t remain, char** arithstart)
730{
731	double x, y, result;
732	char operator;
733	int skip;
734	char buf[32];
735	char* at;
736	/* not yet done? we want number operand number expanded first. */
737	if(!*arithstart) {
738		/* remember start pos of expr, skip the first number */
739		at = orig;
740		*arithstart = at;
741		while(*at && (isdigit((unsigned char)*at) || *at == '.'))
742			at++;
743		return at;
744	}
745	/* move back to start */
746	remain += (size_t)(orig - *arithstart);
747	at = *arithstart;
748
749	/* parse operands */
750	if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
751		*arithstart = NULL;
752		return do_macro_arith(orig, remain, arithstart);
753	}
754	if(isdigit((unsigned char)operator)) {
755		*arithstart = orig;
756		return at+skip; /* do nothing, but setup for later number */
757	}
758
759	/* calculate result */
760	if(!perform_arith(x, operator, y, &result)) {
761		log_err("unknown operator: %s", at);
762		return NULL;
763	}
764
765	/* put result back in buffer */
766	snprintf(buf, sizeof(buf), "%.12g", result);
767	if(!do_buf_insert(at, remain, at+skip, buf))
768		return NULL;
769
770	/* the result can be part of another expression, restart that */
771	*arithstart = NULL;
772	return at;
773}
774
775/** Do range macro on expanded buffer */
776static char*
777do_macro_range(char* buf)
778{
779	double x, y, z;
780	if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
781		log_err("range func requires 3 args: %s", buf);
782		return NULL;
783	}
784	if(x <= y && y <= z) {
785		char res[1024];
786		snprintf(res, sizeof(res), "%.24g", y);
787		return strdup(res);
788	}
789	fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
790	return NULL;
791}
792
793static char*
794macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
795{
796	char buf[10240];
797	char* at = *text;
798	size_t len = macro_length(at);
799	int dofunc = 0;
800	char* arithstart = NULL;
801	if(len >= sizeof(buf))
802		return NULL; /* too long */
803	buf[0] = 0;
804	(void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
805	at = buf;
806
807	/* check for functions */
808	if(strcmp(buf, "time") == 0) {
809		if(runtime)
810			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
811		else
812			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)0);
813		*text += len;
814		return strdup(buf);
815	} else if(strcmp(buf, "timeout") == 0) {
816		time_t res = 0;
817		if(runtime) {
818			struct fake_timer* t = first_timer(runtime);
819			if(t && (time_t)t->tv.tv_sec >= runtime->now_secs)
820				res = (time_t)t->tv.tv_sec - runtime->now_secs;
821		}
822		snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
823		*text += len;
824		return strdup(buf);
825	} else if(strncmp(buf, "ctime ", 6) == 0 ||
826		strncmp(buf, "ctime\t", 6) == 0) {
827		at += 6;
828		dofunc = 1;
829	} else if(strncmp(buf, "range ", 6) == 0 ||
830		strncmp(buf, "range\t", 6) == 0) {
831		at += 6;
832		dofunc = 1;
833	}
834
835	/* actual macro text expansion */
836	while(*at) {
837		size_t remain = sizeof(buf)-strlen(buf);
838		if(strncmp(at, "${", 2) == 0) {
839			at = do_macro_recursion(store, runtime, at, remain);
840		} else if(*at == '$') {
841			at = do_macro_variable(store, at, remain);
842		} else if(isdigit((unsigned char)*at)) {
843			at = do_macro_arith(at, remain, &arithstart);
844		} else {
845			/* copy until whitespace or operator */
846			if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
847				at++;
848				while(*at && (isalnum((unsigned char)*at) || *at=='_'))
849					at++;
850			} else at++;
851		}
852		if(!at) return NULL; /* failure */
853	}
854	*text += len;
855	if(dofunc) {
856		/* post process functions, buf has the argument(s) */
857		if(strncmp(buf, "ctime", 5) == 0) {
858			return do_macro_ctime(buf+6);
859		} else if(strncmp(buf, "range", 5) == 0) {
860			return do_macro_range(buf+6);
861		}
862	}
863	return strdup(buf);
864}
865
866char*
867macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
868{
869	char buf[10240];
870	char* next, *expand;
871	char* at = text;
872	if(!strstr(text, "${"))
873		return strdup(text); /* no macros */
874	buf[0] = 0;
875	buf[sizeof(buf)-1]=0;
876	while( (next=strstr(at, "${")) ) {
877		/* copy text before next macro */
878		if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
879			return NULL; /* string too long */
880		(void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
881		/* process the macro itself */
882		next += 2;
883		expand = macro_expand(store, runtime, &next);
884		if(!expand) return NULL; /* expansion failed */
885		(void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
886		free(expand);
887		at = next;
888	}
889	/* copy remainder fixed text */
890	(void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
891	return strdup(buf);
892}
893
894char*
895macro_lookup(rbtree_type* store, char* name)
896{
897	struct replay_var* x = macro_getvar(store, name);
898	if(!x) return strdup("");
899	return strdup(x->value);
900}
901
902void macro_print_debug(rbtree_type* store)
903{
904	struct replay_var* x;
905	RBTREE_FOR(x, struct replay_var*, store) {
906		log_info("%s = %s", x->name, x->value);
907	}
908}
909
910int
911macro_assign(rbtree_type* store, char* name, char* value)
912{
913	struct replay_var* x = macro_getvar(store, name);
914	if(x) {
915		free(x->value);
916	} else {
917		x = (struct replay_var*)malloc(sizeof(*x));
918		if(!x) return 0;
919		x->node.key = x;
920		x->name = strdup(name);
921		if(!x->name) {
922			free(x);
923			return 0;
924		}
925		(void)rbtree_insert(store, &x->node);
926	}
927	x->value = strdup(value);
928	return x->value != NULL;
929}
930
931/* testbound assert function for selftest.  counts the number of tests */
932#define tb_assert(x) \
933	do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
934		__FILE__, __LINE__, __func__, #x); \
935		num_asserts++; \
936	} while(0);
937
938void testbound_selftest(void)
939{
940	/* test the macro store */
941	rbtree_type* store = macro_store_create();
942	char* v;
943	int r;
944	int num_asserts = 0;
945	tb_assert(store);
946
947	v = macro_lookup(store, "bla");
948	tb_assert(strcmp(v, "") == 0);
949	free(v);
950
951	v = macro_lookup(store, "vlerk");
952	tb_assert(strcmp(v, "") == 0);
953	free(v);
954
955	r = macro_assign(store, "bla", "waarde1");
956	tb_assert(r);
957
958	v = macro_lookup(store, "vlerk");
959	tb_assert(strcmp(v, "") == 0);
960	free(v);
961
962	v = macro_lookup(store, "bla");
963	tb_assert(strcmp(v, "waarde1") == 0);
964	free(v);
965
966	r = macro_assign(store, "vlerk", "kanteel");
967	tb_assert(r);
968
969	v = macro_lookup(store, "bla");
970	tb_assert(strcmp(v, "waarde1") == 0);
971	free(v);
972
973	v = macro_lookup(store, "vlerk");
974	tb_assert(strcmp(v, "kanteel") == 0);
975	free(v);
976
977	r = macro_assign(store, "bla", "ww");
978	tb_assert(r);
979
980	v = macro_lookup(store, "bla");
981	tb_assert(strcmp(v, "ww") == 0);
982	free(v);
983
984	tb_assert( macro_length("}") == 1);
985	tb_assert( macro_length("blabla}") == 7);
986	tb_assert( macro_length("bla${zoink}bla}") == 7+8);
987	tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
988
989	v = macro_process(store, NULL, "");
990	tb_assert( v && strcmp(v, "") == 0);
991	free(v);
992
993	v = macro_process(store, NULL, "${}");
994	tb_assert( v && strcmp(v, "") == 0);
995	free(v);
996
997	v = macro_process(store, NULL, "blabla ${} dinges");
998	tb_assert( v && strcmp(v, "blabla  dinges") == 0);
999	free(v);
1000
1001	v = macro_process(store, NULL, "1${$bla}2${$bla}3");
1002	tb_assert( v && strcmp(v, "1ww2ww3") == 0);
1003	free(v);
1004
1005	v = macro_process(store, NULL, "it is ${ctime 123456}");
1006	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1007	free(v);
1008
1009	r = macro_assign(store, "t1", "123456");
1010	tb_assert(r);
1011	v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
1012	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1013	free(v);
1014
1015	v = macro_process(store, NULL, "it is ${ctime $t1}");
1016	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1017	free(v);
1018
1019	r = macro_assign(store, "x", "1");
1020	tb_assert(r);
1021	r = macro_assign(store, "y", "2");
1022	tb_assert(r);
1023	v = macro_process(store, NULL, "${$x + $x}");
1024	tb_assert( v && strcmp(v, "2") == 0);
1025	free(v);
1026	v = macro_process(store, NULL, "${$x - $x}");
1027	tb_assert( v && strcmp(v, "0") == 0);
1028	free(v);
1029	v = macro_process(store, NULL, "${$y * $y}");
1030	tb_assert( v && strcmp(v, "4") == 0);
1031	free(v);
1032	v = macro_process(store, NULL, "${32 / $y + $x + $y}");
1033	tb_assert( v && strcmp(v, "19") == 0);
1034	free(v);
1035
1036	v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
1037	tb_assert( v && strcmp(v, "108") == 0);
1038	free(v);
1039
1040	v = macro_process(store, NULL, "${1 2 33 2 1}");
1041	tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
1042	free(v);
1043
1044	v = macro_process(store, NULL, "${123 3 + 5}");
1045	tb_assert( v && strcmp(v, "123 8") == 0);
1046	free(v);
1047
1048	v = macro_process(store, NULL, "${123 glug 3 + 5}");
1049	tb_assert( v && strcmp(v, "123 glug 8") == 0);
1050	free(v);
1051
1052	macro_store_delete(store);
1053	printf("selftest successful (%d checks).\n", num_asserts);
1054}
1055