xref: /openbsd-src/usr.sbin/unbound/testcode/replay.c (revision 2682a17cb638eafae4cf33c5419bfe5b11d3c186)
1712b2f30Ssthen /*
2712b2f30Ssthen  * testcode/replay.c - store and use a replay of events for the DNS resolver.
3712b2f30Ssthen  *
4712b2f30Ssthen  * Copyright (c) 2007, NLnet Labs. All rights reserved.
5712b2f30Ssthen  *
6712b2f30Ssthen  * This software is open source.
7712b2f30Ssthen  *
8712b2f30Ssthen  * Redistribution and use in source and binary forms, with or without
9712b2f30Ssthen  * modification, are permitted provided that the following conditions
10712b2f30Ssthen  * are met:
11712b2f30Ssthen  *
12712b2f30Ssthen  * Redistributions of source code must retain the above copyright notice,
13712b2f30Ssthen  * this list of conditions and the following disclaimer.
14712b2f30Ssthen  *
15712b2f30Ssthen  * Redistributions in binary form must reproduce the above copyright notice,
16712b2f30Ssthen  * this list of conditions and the following disclaimer in the documentation
17712b2f30Ssthen  * and/or other materials provided with the distribution.
18712b2f30Ssthen  *
19712b2f30Ssthen  * Neither the name of the NLNET LABS nor the names of its contributors may
20712b2f30Ssthen  * be used to endorse or promote products derived from this software without
21712b2f30Ssthen  * specific prior written permission.
22712b2f30Ssthen  *
23712b2f30Ssthen  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24712b2f30Ssthen  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25712b2f30Ssthen  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26712b2f30Ssthen  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27712b2f30Ssthen  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28712b2f30Ssthen  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29712b2f30Ssthen  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30712b2f30Ssthen  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31712b2f30Ssthen  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32712b2f30Ssthen  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33712b2f30Ssthen  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34712b2f30Ssthen  */
35712b2f30Ssthen 
36712b2f30Ssthen /**
37712b2f30Ssthen  * \file
38712b2f30Ssthen  * Store and use a replay of events for the DNS resolver.
39712b2f30Ssthen  * Used to test known scenarios to get known outcomes.
40712b2f30Ssthen  */
41712b2f30Ssthen 
42712b2f30Ssthen #include "config.h"
43712b2f30Ssthen /* for strtod prototype */
44712b2f30Ssthen #include <math.h>
45712b2f30Ssthen #include <ctype.h>
46712b2f30Ssthen #include <time.h>
47712b2f30Ssthen #include "util/log.h"
48712b2f30Ssthen #include "util/net_help.h"
49712b2f30Ssthen #include "util/config_file.h"
50712b2f30Ssthen #include "testcode/replay.h"
51712b2f30Ssthen #include "testcode/testpkts.h"
52712b2f30Ssthen #include "testcode/fake_event.h"
53712b2f30Ssthen #include "sldns/str2wire.h"
54437d2860Ssthen #include "util/timeval_func.h"
55712b2f30Ssthen 
56712b2f30Ssthen /** max length of lines in file */
57712b2f30Ssthen #define MAX_LINE_LEN 10240
58712b2f30Ssthen 
59712b2f30Ssthen /**
60712b2f30Ssthen  * Expand a macro
61712b2f30Ssthen  * @param store: value storage
62712b2f30Ssthen  * @param runtime: replay runtime for other stuff.
63712b2f30Ssthen  * @param text: the macro text, after the ${, Updated to after the } when
64712b2f30Ssthen  * 	done (successfully).
65712b2f30Ssthen  * @return expanded text, malloced. NULL on failure.
66712b2f30Ssthen  */
67712b2f30Ssthen static char* macro_expand(rbtree_type* store,
68712b2f30Ssthen 	struct replay_runtime* runtime, char** text);
69712b2f30Ssthen 
70712b2f30Ssthen /** parse keyword in string.
71712b2f30Ssthen  * @param line: if found, the line is advanced to after the keyword.
72712b2f30Ssthen  * @param keyword: string.
73712b2f30Ssthen  * @return: true if found, false if not.
74712b2f30Ssthen  */
75712b2f30Ssthen static int
parse_keyword(char ** line,const char * keyword)76712b2f30Ssthen parse_keyword(char** line, const char* keyword)
77712b2f30Ssthen {
78712b2f30Ssthen 	size_t len = (size_t)strlen(keyword);
79712b2f30Ssthen 	if(strncmp(*line, keyword, len) == 0) {
80712b2f30Ssthen 		*line += len;
81712b2f30Ssthen 		return 1;
82712b2f30Ssthen 	}
83712b2f30Ssthen 	return 0;
84712b2f30Ssthen }
85712b2f30Ssthen 
86712b2f30Ssthen /** delete moment */
87712b2f30Ssthen static void
replay_moment_delete(struct replay_moment * mom)88712b2f30Ssthen replay_moment_delete(struct replay_moment* mom)
89712b2f30Ssthen {
90712b2f30Ssthen 	if(!mom)
91712b2f30Ssthen 		return;
92712b2f30Ssthen 	if(mom->match) {
93712b2f30Ssthen 		delete_entry(mom->match);
94712b2f30Ssthen 	}
95712b2f30Ssthen 	free(mom->autotrust_id);
96712b2f30Ssthen 	free(mom->string);
97712b2f30Ssthen 	free(mom->variable);
98712b2f30Ssthen 	config_delstrlist(mom->file_content);
99712b2f30Ssthen 	free(mom);
100712b2f30Ssthen }
101712b2f30Ssthen 
102712b2f30Ssthen /** delete range */
103712b2f30Ssthen static void
replay_range_delete(struct replay_range * rng)104712b2f30Ssthen replay_range_delete(struct replay_range* rng)
105712b2f30Ssthen {
106712b2f30Ssthen 	if(!rng)
107712b2f30Ssthen 		return;
108712b2f30Ssthen 	delete_entry(rng->match);
109712b2f30Ssthen 	free(rng);
110712b2f30Ssthen }
111712b2f30Ssthen 
112a6cc1574Ssthen void
strip_end_white(char * p)113712b2f30Ssthen strip_end_white(char* p)
114712b2f30Ssthen {
115712b2f30Ssthen 	size_t i;
116712b2f30Ssthen 	for(i = strlen(p); i > 0; i--) {
117712b2f30Ssthen 		if(isspace((unsigned char)p[i-1]))
118712b2f30Ssthen 			p[i-1] = 0;
119712b2f30Ssthen 		else return;
120712b2f30Ssthen 	}
121712b2f30Ssthen }
122712b2f30Ssthen 
123712b2f30Ssthen /**
124712b2f30Ssthen  * Read a range from file.
125712b2f30Ssthen  * @param remain: Rest of line (after RANGE keyword).
126712b2f30Ssthen  * @param in: file to read from.
127712b2f30Ssthen  * @param name: name to print in errors.
128712b2f30Ssthen  * @param pstate: read state structure with
129712b2f30Ssthen  * 	with lineno : incremented as lines are read.
130712b2f30Ssthen  * 	ttl, origin, prev for readentry.
131712b2f30Ssthen  * @param line: line buffer.
132712b2f30Ssthen  * @return: range object to add to list, or NULL on error.
133712b2f30Ssthen  */
134712b2f30Ssthen static struct replay_range*
replay_range_read(char * remain,FILE * in,const char * name,struct sldns_file_parse_state * pstate,char * line)135712b2f30Ssthen replay_range_read(char* remain, FILE* in, const char* name,
136712b2f30Ssthen 	struct sldns_file_parse_state* pstate, char* line)
137712b2f30Ssthen {
138712b2f30Ssthen 	struct replay_range* rng = (struct replay_range*)malloc(
139712b2f30Ssthen 		sizeof(struct replay_range));
140712b2f30Ssthen 	off_t pos;
141712b2f30Ssthen 	char *parse;
142712b2f30Ssthen 	struct entry* entry, *last = NULL;
143712b2f30Ssthen 	if(!rng)
144712b2f30Ssthen 		return NULL;
145712b2f30Ssthen 	memset(rng, 0, sizeof(*rng));
146712b2f30Ssthen 	/* read time range */
147712b2f30Ssthen 	if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
148712b2f30Ssthen 		log_err("Could not read time range: %s", line);
149712b2f30Ssthen 		free(rng);
150712b2f30Ssthen 		return NULL;
151712b2f30Ssthen 	}
152712b2f30Ssthen 	/* read entries */
153712b2f30Ssthen 	pos = ftello(in);
154712b2f30Ssthen 	while(fgets(line, MAX_LINE_LEN-1, in)) {
155712b2f30Ssthen 		pstate->lineno++;
156712b2f30Ssthen 		parse = line;
157712b2f30Ssthen 		while(isspace((unsigned char)*parse))
158712b2f30Ssthen 			parse++;
159712b2f30Ssthen 		if(!*parse || *parse == ';') {
160712b2f30Ssthen 			pos = ftello(in);
161712b2f30Ssthen 			continue;
162712b2f30Ssthen 		}
163712b2f30Ssthen 		if(parse_keyword(&parse, "ADDRESS")) {
164712b2f30Ssthen 			while(isspace((unsigned char)*parse))
165712b2f30Ssthen 				parse++;
166712b2f30Ssthen 			strip_end_white(parse);
1670e9b6f9fSsthen 			if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen,
1680e9b6f9fSsthen 				UNBOUND_DNS_PORT)) {
169712b2f30Ssthen 				log_err("Line %d: could not read ADDRESS: %s",
170712b2f30Ssthen 					pstate->lineno, parse);
171712b2f30Ssthen 				free(rng);
172712b2f30Ssthen 				return NULL;
173712b2f30Ssthen 			}
174712b2f30Ssthen 			pos = ftello(in);
175712b2f30Ssthen 			continue;
176712b2f30Ssthen 		}
177712b2f30Ssthen 		if(parse_keyword(&parse, "RANGE_END")) {
178712b2f30Ssthen 			return rng;
179712b2f30Ssthen 		}
180712b2f30Ssthen 		/* set position before line; read entry */
181712b2f30Ssthen 		pstate->lineno--;
182712b2f30Ssthen 		fseeko(in, pos, SEEK_SET);
183712b2f30Ssthen 		entry = read_entry(in, name, pstate, 1);
184712b2f30Ssthen 		if(!entry)
185712b2f30Ssthen 			fatal_exit("%d: bad entry", pstate->lineno);
186712b2f30Ssthen 		entry->next = NULL;
187712b2f30Ssthen 		if(last)
188712b2f30Ssthen 			last->next = entry;
189712b2f30Ssthen 		else	rng->match = entry;
190712b2f30Ssthen 		last = entry;
191712b2f30Ssthen 
192712b2f30Ssthen 		pos = ftello(in);
193712b2f30Ssthen 	}
194712b2f30Ssthen 	replay_range_delete(rng);
195712b2f30Ssthen 	return NULL;
196712b2f30Ssthen }
197712b2f30Ssthen 
198712b2f30Ssthen /** Read FILE match content */
199712b2f30Ssthen static void
read_file_content(FILE * in,int * lineno,struct replay_moment * mom)200712b2f30Ssthen read_file_content(FILE* in, int* lineno, struct replay_moment* mom)
201712b2f30Ssthen {
202712b2f30Ssthen 	char line[MAX_LINE_LEN];
203712b2f30Ssthen 	char* remain = line;
204712b2f30Ssthen 	struct config_strlist** last = &mom->file_content;
205712b2f30Ssthen 	line[MAX_LINE_LEN-1]=0;
206712b2f30Ssthen 	if(!fgets(line, MAX_LINE_LEN-1, in))
207712b2f30Ssthen 		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
208712b2f30Ssthen 	if(!parse_keyword(&remain, "FILE_BEGIN"))
209712b2f30Ssthen 		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
210712b2f30Ssthen 	while(fgets(line, MAX_LINE_LEN-1, in)) {
211712b2f30Ssthen 		(*lineno)++;
212712b2f30Ssthen 		if(strncmp(line, "FILE_END", 8) == 0) {
213712b2f30Ssthen 			return;
214712b2f30Ssthen 		}
215a6cc1574Ssthen 		strip_end_white(line);
216712b2f30Ssthen 		if(!cfg_strlist_insert(last, strdup(line)))
217712b2f30Ssthen 			fatal_exit("malloc failure");
218712b2f30Ssthen 		last = &( (*last)->next );
219712b2f30Ssthen 	}
220712b2f30Ssthen 	fatal_exit("no FILE_END in input file");
221712b2f30Ssthen }
222712b2f30Ssthen 
223712b2f30Ssthen /** read assign step info */
224712b2f30Ssthen static void
read_assign_step(char * remain,struct replay_moment * mom)225712b2f30Ssthen read_assign_step(char* remain, struct replay_moment* mom)
226712b2f30Ssthen {
227712b2f30Ssthen 	char buf[1024];
228712b2f30Ssthen 	char eq;
229712b2f30Ssthen 	int skip;
230712b2f30Ssthen 	buf[sizeof(buf)-1]=0;
231712b2f30Ssthen 	if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2)
232712b2f30Ssthen 		fatal_exit("cannot parse assign: %s", remain);
233712b2f30Ssthen 	mom->variable = strdup(buf);
234712b2f30Ssthen 	if(eq != '=')
235712b2f30Ssthen 		fatal_exit("no '=' in assign: %s", remain);
236712b2f30Ssthen 	remain += skip;
237a6cc1574Ssthen 	strip_end_white(remain);
238712b2f30Ssthen 	mom->string = strdup(remain);
239712b2f30Ssthen 	if(!mom->variable || !mom->string)
240712b2f30Ssthen 		fatal_exit("out of memory");
241712b2f30Ssthen }
242712b2f30Ssthen 
243712b2f30Ssthen /**
244712b2f30Ssthen  * Read a replay moment 'STEP' from file.
245712b2f30Ssthen  * @param remain: Rest of line (after STEP keyword).
246712b2f30Ssthen  * @param in: file to read from.
247712b2f30Ssthen  * @param name: name to print in errors.
248712b2f30Ssthen  * @param pstate: with lineno, ttl, origin, prev for parse state.
249712b2f30Ssthen  * 	lineno is incremented.
250712b2f30Ssthen  * @return: range object to add to list, or NULL on error.
251712b2f30Ssthen  */
252712b2f30Ssthen static struct replay_moment*
replay_moment_read(char * remain,FILE * in,const char * name,struct sldns_file_parse_state * pstate)253712b2f30Ssthen replay_moment_read(char* remain, FILE* in, const char* name,
254712b2f30Ssthen 	struct sldns_file_parse_state* pstate)
255712b2f30Ssthen {
256712b2f30Ssthen 	struct replay_moment* mom = (struct replay_moment*)malloc(
257712b2f30Ssthen 		sizeof(struct replay_moment));
258712b2f30Ssthen 	int skip = 0;
259712b2f30Ssthen 	int readentry = 0;
260712b2f30Ssthen 	if(!mom)
261712b2f30Ssthen 		return NULL;
262712b2f30Ssthen 	memset(mom, 0, sizeof(*mom));
263712b2f30Ssthen 	if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
264712b2f30Ssthen 		log_err("%d: cannot read number: %s", pstate->lineno, remain);
265712b2f30Ssthen 		free(mom);
266712b2f30Ssthen 		return NULL;
267712b2f30Ssthen 	}
268712b2f30Ssthen 	remain += skip;
269712b2f30Ssthen 	while(isspace((unsigned char)*remain))
270712b2f30Ssthen 		remain++;
271712b2f30Ssthen 	if(parse_keyword(&remain, "NOTHING")) {
272712b2f30Ssthen 		mom->evt_type = repevt_nothing;
273712b2f30Ssthen 	} else if(parse_keyword(&remain, "QUERY")) {
274712b2f30Ssthen 		mom->evt_type = repevt_front_query;
275712b2f30Ssthen 		readentry = 1;
2760e9b6f9fSsthen 		if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen,
2770e9b6f9fSsthen 			UNBOUND_DNS_PORT))
278712b2f30Ssthen 			fatal_exit("internal error");
279712b2f30Ssthen 	} else if(parse_keyword(&remain, "CHECK_ANSWER")) {
280712b2f30Ssthen 		mom->evt_type = repevt_front_reply;
281712b2f30Ssthen 		readentry = 1;
282712b2f30Ssthen 	} else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
283712b2f30Ssthen 		mom->evt_type = repevt_back_query;
284712b2f30Ssthen 		readentry = 1;
285712b2f30Ssthen 	} else if(parse_keyword(&remain, "REPLY")) {
286712b2f30Ssthen 		mom->evt_type = repevt_back_reply;
287712b2f30Ssthen 		readentry = 1;
288712b2f30Ssthen 	} else if(parse_keyword(&remain, "TIMEOUT")) {
289712b2f30Ssthen 		mom->evt_type = repevt_timeout;
290712b2f30Ssthen 	} else if(parse_keyword(&remain, "TIME_PASSES")) {
291712b2f30Ssthen 		mom->evt_type = repevt_time_passes;
292712b2f30Ssthen 		while(isspace((unsigned char)*remain))
293712b2f30Ssthen 			remain++;
294712b2f30Ssthen 		if(parse_keyword(&remain, "EVAL")) {
295712b2f30Ssthen 			while(isspace((unsigned char)*remain))
296712b2f30Ssthen 				remain++;
297712b2f30Ssthen 			mom->string = strdup(remain);
298712b2f30Ssthen 			if(!mom->string) fatal_exit("out of memory");
299712b2f30Ssthen 			if(strlen(mom->string)>0)
300712b2f30Ssthen 				mom->string[strlen(mom->string)-1]=0;
301712b2f30Ssthen 			remain += strlen(mom->string);
302712b2f30Ssthen 		}
303712b2f30Ssthen 	} else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) {
304712b2f30Ssthen 		mom->evt_type = repevt_autotrust_check;
305712b2f30Ssthen 		while(isspace((unsigned char)*remain))
306712b2f30Ssthen 			remain++;
307a6cc1574Ssthen 		strip_end_white(remain);
308712b2f30Ssthen 		mom->autotrust_id = strdup(remain);
309712b2f30Ssthen 		if(!mom->autotrust_id) fatal_exit("out of memory");
310712b2f30Ssthen 		read_file_content(in, &pstate->lineno, mom);
311712b2f30Ssthen 	} else if(parse_keyword(&remain, "CHECK_TEMPFILE")) {
312712b2f30Ssthen 		mom->evt_type = repevt_tempfile_check;
313712b2f30Ssthen 		while(isspace((unsigned char)*remain))
314712b2f30Ssthen 			remain++;
315a6cc1574Ssthen 		strip_end_white(remain);
316712b2f30Ssthen 		mom->autotrust_id = strdup(remain);
317712b2f30Ssthen 		if(!mom->autotrust_id) fatal_exit("out of memory");
318712b2f30Ssthen 		read_file_content(in, &pstate->lineno, mom);
319712b2f30Ssthen 	} else if(parse_keyword(&remain, "ERROR")) {
320712b2f30Ssthen 		mom->evt_type = repevt_error;
321712b2f30Ssthen 	} else if(parse_keyword(&remain, "TRAFFIC")) {
322712b2f30Ssthen 		mom->evt_type = repevt_traffic;
323712b2f30Ssthen 	} else if(parse_keyword(&remain, "ASSIGN")) {
324712b2f30Ssthen 		mom->evt_type = repevt_assign;
325712b2f30Ssthen 		read_assign_step(remain, mom);
326712b2f30Ssthen 	} else if(parse_keyword(&remain, "INFRA_RTT")) {
327712b2f30Ssthen 		char *s, *m;
328712b2f30Ssthen 		mom->evt_type = repevt_infra_rtt;
329712b2f30Ssthen 		while(isspace((unsigned char)*remain))
330712b2f30Ssthen 			remain++;
331712b2f30Ssthen 		s = remain;
332712b2f30Ssthen 		remain = strchr(s, ' ');
333712b2f30Ssthen 		if(!remain) fatal_exit("expected three args for INFRA_RTT");
334712b2f30Ssthen 		remain[0] = 0;
335712b2f30Ssthen 		remain++;
336712b2f30Ssthen 		while(isspace((unsigned char)*remain))
337712b2f30Ssthen 			remain++;
338712b2f30Ssthen 		m = strchr(remain, ' ');
339712b2f30Ssthen 		if(!m) fatal_exit("expected three args for INFRA_RTT");
340712b2f30Ssthen 		m[0] = 0;
341712b2f30Ssthen 		m++;
342712b2f30Ssthen 		while(isspace((unsigned char)*m))
343712b2f30Ssthen 			m++;
3440e9b6f9fSsthen 		if(!extstrtoaddr(s, &mom->addr, &mom->addrlen, UNBOUND_DNS_PORT))
345712b2f30Ssthen 			fatal_exit("bad infra_rtt address %s", s);
346a6cc1574Ssthen 		strip_end_white(m);
347712b2f30Ssthen 		mom->variable = strdup(remain);
348712b2f30Ssthen 		mom->string = strdup(m);
349712b2f30Ssthen 		if(!mom->string) fatal_exit("out of memory");
350712b2f30Ssthen 		if(!mom->variable) fatal_exit("out of memory");
351*2682a17cSsthen 	} else if(parse_keyword(&remain, "FLUSH_MESSAGE")) {
352*2682a17cSsthen 		mom->evt_type = repevt_flush_message;
353*2682a17cSsthen 		while(isspace((unsigned char)*remain))
354*2682a17cSsthen 			remain++;
355*2682a17cSsthen 		strip_end_white(remain);
356*2682a17cSsthen 		mom->string = strdup(remain);
357*2682a17cSsthen 		if(!mom->string) fatal_exit("out of memory");
358*2682a17cSsthen 	} else if(parse_keyword(&remain, "EXPIRE_MESSAGE")) {
359*2682a17cSsthen 		mom->evt_type = repevt_expire_message;
360*2682a17cSsthen 		while(isspace((unsigned char)*remain))
361*2682a17cSsthen 			remain++;
362*2682a17cSsthen 		strip_end_white(remain);
363*2682a17cSsthen 		mom->string = strdup(remain);
364*2682a17cSsthen 		if(!mom->string) fatal_exit("out of memory");
365712b2f30Ssthen 	} else {
366712b2f30Ssthen 		log_err("%d: unknown event type %s", pstate->lineno, remain);
367712b2f30Ssthen 		free(mom);
368712b2f30Ssthen 		return NULL;
369712b2f30Ssthen 	}
370712b2f30Ssthen 	while(isspace((unsigned char)*remain))
371712b2f30Ssthen 		remain++;
372712b2f30Ssthen 	if(parse_keyword(&remain, "ADDRESS")) {
373712b2f30Ssthen 		while(isspace((unsigned char)*remain))
374712b2f30Ssthen 			remain++;
375a6cc1574Ssthen 		strip_end_white(remain);
3760e9b6f9fSsthen 		if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen,
3770e9b6f9fSsthen 			UNBOUND_DNS_PORT)) {
378712b2f30Ssthen 			log_err("line %d: could not parse ADDRESS: %s",
379712b2f30Ssthen 				pstate->lineno, remain);
380712b2f30Ssthen 			free(mom);
381712b2f30Ssthen 			return NULL;
382712b2f30Ssthen 		}
383712b2f30Ssthen 	}
384712b2f30Ssthen 	if(parse_keyword(&remain, "ELAPSE")) {
385712b2f30Ssthen 		double sec;
386712b2f30Ssthen 		errno = 0;
387712b2f30Ssthen 		sec = strtod(remain, &remain);
388712b2f30Ssthen 		if(sec == 0. && errno != 0) {
389712b2f30Ssthen 			log_err("line %d: could not parse ELAPSE: %s (%s)",
390712b2f30Ssthen 				pstate->lineno, remain, strerror(errno));
391712b2f30Ssthen 			free(mom);
392712b2f30Ssthen 			return NULL;
393712b2f30Ssthen 		}
394712b2f30Ssthen #ifndef S_SPLINT_S
395712b2f30Ssthen 		mom->elapse.tv_sec = (int)sec;
396712b2f30Ssthen 		mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
397712b2f30Ssthen 			*1000000. + 0.5);
398712b2f30Ssthen #endif
399712b2f30Ssthen 	}
400712b2f30Ssthen 
401712b2f30Ssthen 	if(readentry) {
402712b2f30Ssthen 		mom->match = read_entry(in, name, pstate, 1);
403712b2f30Ssthen 		if(!mom->match) {
404712b2f30Ssthen 			free(mom);
405712b2f30Ssthen 			return NULL;
406712b2f30Ssthen 		}
407712b2f30Ssthen 	}
408712b2f30Ssthen 
409712b2f30Ssthen 	return mom;
410712b2f30Ssthen }
411712b2f30Ssthen 
412712b2f30Ssthen /** makes scenario with title on rest of line */
413712b2f30Ssthen static struct replay_scenario*
make_scenario(char * line)414712b2f30Ssthen make_scenario(char* line)
415712b2f30Ssthen {
416712b2f30Ssthen 	struct replay_scenario* scen;
417712b2f30Ssthen 	while(isspace((unsigned char)*line))
418712b2f30Ssthen 		line++;
419712b2f30Ssthen 	if(!*line) {
420712b2f30Ssthen 		log_err("scenario: no title given");
421712b2f30Ssthen 		return NULL;
422712b2f30Ssthen 	}
423712b2f30Ssthen 	scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
424712b2f30Ssthen 	if(!scen)
425712b2f30Ssthen 		return NULL;
426712b2f30Ssthen 	memset(scen, 0, sizeof(*scen));
427712b2f30Ssthen 	scen->title = strdup(line);
428712b2f30Ssthen 	if(!scen->title) {
429712b2f30Ssthen 		free(scen);
430712b2f30Ssthen 		return NULL;
431712b2f30Ssthen 	}
432712b2f30Ssthen 	return scen;
433712b2f30Ssthen }
434712b2f30Ssthen 
435712b2f30Ssthen struct replay_scenario*
replay_scenario_read(FILE * in,const char * name,int * lineno)436712b2f30Ssthen replay_scenario_read(FILE* in, const char* name, int* lineno)
437712b2f30Ssthen {
438712b2f30Ssthen 	char line[MAX_LINE_LEN];
439712b2f30Ssthen 	char *parse;
440712b2f30Ssthen 	struct replay_scenario* scen = NULL;
441712b2f30Ssthen 	struct sldns_file_parse_state pstate;
442712b2f30Ssthen 	line[MAX_LINE_LEN-1]=0;
443712b2f30Ssthen 	memset(&pstate, 0, sizeof(pstate));
444712b2f30Ssthen 	pstate.default_ttl = 3600;
445712b2f30Ssthen 	pstate.lineno = *lineno;
446712b2f30Ssthen 
447712b2f30Ssthen 	while(fgets(line, MAX_LINE_LEN-1, in)) {
448712b2f30Ssthen 		parse=line;
449712b2f30Ssthen 		pstate.lineno++;
450712b2f30Ssthen 		(*lineno)++;
451712b2f30Ssthen 		while(isspace((unsigned char)*parse))
452712b2f30Ssthen 			parse++;
453712b2f30Ssthen 		if(!*parse)
454712b2f30Ssthen 			continue; /* empty line */
455712b2f30Ssthen 		if(parse_keyword(&parse, ";"))
456712b2f30Ssthen 			continue; /* comment */
457712b2f30Ssthen 		if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
4588771e50fSsthen 			if(scen)
4598771e50fSsthen 				fatal_exit("%d: double SCENARIO_BEGIN", *lineno);
460712b2f30Ssthen 			scen = make_scenario(parse);
461712b2f30Ssthen 			if(!scen)
462712b2f30Ssthen 				fatal_exit("%d: could not make scen", *lineno);
463712b2f30Ssthen 			continue;
464712b2f30Ssthen 		}
465712b2f30Ssthen 		if(!scen)
466712b2f30Ssthen 			fatal_exit("%d: expected SCENARIO", *lineno);
467712b2f30Ssthen 		if(parse_keyword(&parse, "RANGE_BEGIN")) {
468712b2f30Ssthen 			struct replay_range* newr = replay_range_read(parse,
469712b2f30Ssthen 				in, name, &pstate, line);
470712b2f30Ssthen 			if(!newr)
471712b2f30Ssthen 				fatal_exit("%d: bad range", pstate.lineno);
472712b2f30Ssthen 			*lineno = pstate.lineno;
473712b2f30Ssthen 			newr->next_range = scen->range_list;
474712b2f30Ssthen 			scen->range_list = newr;
475712b2f30Ssthen 		} else if(parse_keyword(&parse, "STEP")) {
476712b2f30Ssthen 			struct replay_moment* mom = replay_moment_read(parse,
477712b2f30Ssthen 				in, name, &pstate);
478712b2f30Ssthen 			if(!mom)
479712b2f30Ssthen 				fatal_exit("%d: bad moment", pstate.lineno);
480712b2f30Ssthen 			*lineno = pstate.lineno;
481712b2f30Ssthen 			if(scen->mom_last &&
482712b2f30Ssthen 				scen->mom_last->time_step >= mom->time_step)
483712b2f30Ssthen 				fatal_exit("%d: time goes backwards", *lineno);
484712b2f30Ssthen 			if(scen->mom_last)
485712b2f30Ssthen 				scen->mom_last->mom_next = mom;
486712b2f30Ssthen 			else	scen->mom_first = mom;
487712b2f30Ssthen 			scen->mom_last = mom;
488712b2f30Ssthen 		} else if(parse_keyword(&parse, "SCENARIO_END")) {
489712b2f30Ssthen 			struct replay_moment *p = scen->mom_first;
490712b2f30Ssthen 			int num = 0;
491712b2f30Ssthen 			while(p) {
492712b2f30Ssthen 				num++;
493712b2f30Ssthen 				p = p->mom_next;
494712b2f30Ssthen 			}
495712b2f30Ssthen 			log_info("Scenario has %d steps", num);
496712b2f30Ssthen 			return scen;
497712b2f30Ssthen 		}
498712b2f30Ssthen 	}
499712b2f30Ssthen 	log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
500712b2f30Ssthen 	replay_scenario_delete(scen);
501712b2f30Ssthen 	return NULL;
502712b2f30Ssthen }
503712b2f30Ssthen 
504712b2f30Ssthen void
replay_scenario_delete(struct replay_scenario * scen)505712b2f30Ssthen replay_scenario_delete(struct replay_scenario* scen)
506712b2f30Ssthen {
507712b2f30Ssthen 	struct replay_moment* mom, *momn;
508712b2f30Ssthen 	struct replay_range* rng, *rngn;
509712b2f30Ssthen 	if(!scen)
510712b2f30Ssthen 		return;
511712b2f30Ssthen 	free(scen->title);
512712b2f30Ssthen 	mom = scen->mom_first;
513712b2f30Ssthen 	while(mom) {
514712b2f30Ssthen 		momn = mom->mom_next;
515712b2f30Ssthen 		replay_moment_delete(mom);
516712b2f30Ssthen 		mom = momn;
517712b2f30Ssthen 	}
518712b2f30Ssthen 	rng = scen->range_list;
519712b2f30Ssthen 	while(rng) {
520712b2f30Ssthen 		rngn = rng->next_range;
521712b2f30Ssthen 		replay_range_delete(rng);
522712b2f30Ssthen 		rng = rngn;
523712b2f30Ssthen 	}
524712b2f30Ssthen 	free(scen);
525712b2f30Ssthen }
526712b2f30Ssthen 
527712b2f30Ssthen /** fetch oldest timer in list that is enabled */
528712b2f30Ssthen static struct fake_timer*
first_timer(struct replay_runtime * runtime)529712b2f30Ssthen first_timer(struct replay_runtime* runtime)
530712b2f30Ssthen {
531712b2f30Ssthen 	struct fake_timer* p, *res = NULL;
532712b2f30Ssthen 	for(p=runtime->timer_list; p; p=p->next) {
533712b2f30Ssthen 		if(!p->enabled)
534712b2f30Ssthen 			continue;
535712b2f30Ssthen 		if(!res)
536712b2f30Ssthen 			res = p;
537712b2f30Ssthen 		else if(timeval_smaller(&p->tv, &res->tv))
538712b2f30Ssthen 			res = p;
539712b2f30Ssthen 	}
540712b2f30Ssthen 	return res;
541712b2f30Ssthen }
542712b2f30Ssthen 
543712b2f30Ssthen struct fake_timer*
replay_get_oldest_timer(struct replay_runtime * runtime)544712b2f30Ssthen replay_get_oldest_timer(struct replay_runtime* runtime)
545712b2f30Ssthen {
546712b2f30Ssthen 	struct fake_timer* t = first_timer(runtime);
547712b2f30Ssthen 	if(t && timeval_smaller(&t->tv, &runtime->now_tv))
548712b2f30Ssthen 		return t;
549712b2f30Ssthen 	return NULL;
550712b2f30Ssthen }
551712b2f30Ssthen 
552712b2f30Ssthen int
replay_var_compare(const void * a,const void * b)553712b2f30Ssthen replay_var_compare(const void* a, const void* b)
554712b2f30Ssthen {
555712b2f30Ssthen 	struct replay_var* x = (struct replay_var*)a;
556712b2f30Ssthen 	struct replay_var* y = (struct replay_var*)b;
557712b2f30Ssthen 	return strcmp(x->name, y->name);
558712b2f30Ssthen }
559712b2f30Ssthen 
560712b2f30Ssthen rbtree_type*
macro_store_create(void)561712b2f30Ssthen macro_store_create(void)
562712b2f30Ssthen {
563712b2f30Ssthen 	return rbtree_create(&replay_var_compare);
564712b2f30Ssthen }
565712b2f30Ssthen 
566712b2f30Ssthen /** helper function to delete macro values */
567712b2f30Ssthen static void
del_macro(rbnode_type * x,void * ATTR_UNUSED (arg))568712b2f30Ssthen del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
569712b2f30Ssthen {
570712b2f30Ssthen 	struct replay_var* v = (struct replay_var*)x;
571712b2f30Ssthen 	free(v->name);
572712b2f30Ssthen 	free(v->value);
573712b2f30Ssthen 	free(v);
574712b2f30Ssthen }
575712b2f30Ssthen 
576712b2f30Ssthen void
macro_store_delete(rbtree_type * store)577712b2f30Ssthen macro_store_delete(rbtree_type* store)
578712b2f30Ssthen {
579712b2f30Ssthen 	if(!store)
580712b2f30Ssthen 		return;
581712b2f30Ssthen 	traverse_postorder(store, del_macro, NULL);
582712b2f30Ssthen 	free(store);
583712b2f30Ssthen }
584712b2f30Ssthen 
585712b2f30Ssthen /** return length of macro */
586712b2f30Ssthen static size_t
macro_length(char * text)587712b2f30Ssthen macro_length(char* text)
588712b2f30Ssthen {
589712b2f30Ssthen 	/* we are after ${, looking for } */
590712b2f30Ssthen 	int depth = 0;
591712b2f30Ssthen 	size_t len = 0;
592712b2f30Ssthen 	while(*text) {
593712b2f30Ssthen 		len++;
594712b2f30Ssthen 		if(*text == '}') {
595712b2f30Ssthen 			if(depth == 0)
596712b2f30Ssthen 				break;
597712b2f30Ssthen 			depth--;
598712b2f30Ssthen 		} else if(text[0] == '$' && text[1] == '{') {
599712b2f30Ssthen 			depth++;
600712b2f30Ssthen 		}
601712b2f30Ssthen 		text++;
602712b2f30Ssthen 	}
603712b2f30Ssthen 	return len;
604712b2f30Ssthen }
605712b2f30Ssthen 
606712b2f30Ssthen /** insert new stuff at start of buffer */
607712b2f30Ssthen static int
do_buf_insert(char * buf,size_t remain,char * after,char * inserted)608712b2f30Ssthen do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
609712b2f30Ssthen {
610712b2f30Ssthen 	char* save = strdup(after);
611712b2f30Ssthen 	size_t len;
612712b2f30Ssthen 	if(!save) return 0;
613712b2f30Ssthen 	if(strlen(inserted) > remain) {
614712b2f30Ssthen 		free(save);
615712b2f30Ssthen 		return 0;
616712b2f30Ssthen 	}
617712b2f30Ssthen 	len = strlcpy(buf, inserted, remain);
618712b2f30Ssthen 	buf += len;
619712b2f30Ssthen 	remain -= len;
620712b2f30Ssthen 	(void)strlcpy(buf, save, remain);
621712b2f30Ssthen 	free(save);
622712b2f30Ssthen 	return 1;
623712b2f30Ssthen }
624712b2f30Ssthen 
625712b2f30Ssthen /** do macro recursion */
626712b2f30Ssthen static char*
do_macro_recursion(rbtree_type * store,struct replay_runtime * runtime,char * at,size_t remain)627712b2f30Ssthen do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
628712b2f30Ssthen 	char* at, size_t remain)
629712b2f30Ssthen {
630712b2f30Ssthen 	char* after = at+2;
631712b2f30Ssthen 	char* expand = macro_expand(store, runtime, &after);
632712b2f30Ssthen 	if(!expand)
633712b2f30Ssthen 		return NULL; /* expansion failed */
634712b2f30Ssthen 	if(!do_buf_insert(at, remain, after, expand)) {
635712b2f30Ssthen 		free(expand);
636712b2f30Ssthen 		return NULL;
637712b2f30Ssthen 	}
638712b2f30Ssthen 	free(expand);
639712b2f30Ssthen 	return at; /* and parse over the expanded text to see if again */
640712b2f30Ssthen }
641712b2f30Ssthen 
642712b2f30Ssthen /** get var from store */
643712b2f30Ssthen static struct replay_var*
macro_getvar(rbtree_type * store,char * name)644712b2f30Ssthen macro_getvar(rbtree_type* store, char* name)
645712b2f30Ssthen {
646712b2f30Ssthen 	struct replay_var k;
647712b2f30Ssthen 	k.node.key = &k;
648712b2f30Ssthen 	k.name = name;
649712b2f30Ssthen 	return (struct replay_var*)rbtree_search(store, &k);
650712b2f30Ssthen }
651712b2f30Ssthen 
652712b2f30Ssthen /** do macro variable */
653712b2f30Ssthen static char*
do_macro_variable(rbtree_type * store,char * buf,size_t remain)654712b2f30Ssthen do_macro_variable(rbtree_type* store, char* buf, size_t remain)
655712b2f30Ssthen {
656712b2f30Ssthen 	struct replay_var* v;
657712b2f30Ssthen 	char* at = buf+1;
658712b2f30Ssthen 	char* name = at;
659712b2f30Ssthen 	char sv;
660712b2f30Ssthen 	if(at[0]==0)
661712b2f30Ssthen 		return NULL; /* no variable name after $ */
662712b2f30Ssthen 	while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
663712b2f30Ssthen 		at++;
664712b2f30Ssthen 	}
665712b2f30Ssthen 	/* terminator, we are working in macro_expand() buffer */
666712b2f30Ssthen 	sv = *at;
667712b2f30Ssthen 	*at = 0;
668712b2f30Ssthen 	v = macro_getvar(store, name);
669712b2f30Ssthen 	*at = sv;
670712b2f30Ssthen 
671712b2f30Ssthen 	if(!v) {
672712b2f30Ssthen 		log_err("variable is not defined: $%s", name);
673712b2f30Ssthen 		return NULL; /* variable undefined is error for now */
674712b2f30Ssthen 	}
675712b2f30Ssthen 
676712b2f30Ssthen 	/* insert the variable contents */
677712b2f30Ssthen 	if(!do_buf_insert(buf, remain, at, v->value))
678712b2f30Ssthen 		return NULL;
679712b2f30Ssthen 	return buf; /* and expand the variable contents */
680712b2f30Ssthen }
681712b2f30Ssthen 
682712b2f30Ssthen /** do ctime macro on argument */
683712b2f30Ssthen static char*
do_macro_ctime(char * arg)684712b2f30Ssthen do_macro_ctime(char* arg)
685712b2f30Ssthen {
686712b2f30Ssthen 	char buf[32];
687712b2f30Ssthen 	time_t tt = (time_t)atoi(arg);
688712b2f30Ssthen 	if(tt == 0 && strcmp(arg, "0") != 0) {
689712b2f30Ssthen 		log_err("macro ctime: expected number, not: %s", arg);
690712b2f30Ssthen 		return NULL;
691712b2f30Ssthen 	}
692712b2f30Ssthen 	ctime_r(&tt, buf);
693a6cc1574Ssthen #ifdef USE_WINSOCK
694a6cc1574Ssthen 	if(strlen(buf) > 10 && buf[7]==' ' && buf[8]=='0')
695a6cc1574Ssthen 		buf[8]=' '; /* fix error in windows ctime */
696a6cc1574Ssthen #endif
697a6cc1574Ssthen 	strip_end_white(buf);
698712b2f30Ssthen 	return strdup(buf);
699712b2f30Ssthen }
700712b2f30Ssthen 
701712b2f30Ssthen /** perform arithmetic operator */
702712b2f30Ssthen static double
perform_arith(double x,char op,double y,double * res)703712b2f30Ssthen perform_arith(double x, char op, double y, double* res)
704712b2f30Ssthen {
705712b2f30Ssthen 	switch(op) {
706712b2f30Ssthen 	case '+':
707712b2f30Ssthen 		*res = x+y;
708712b2f30Ssthen 		break;
709712b2f30Ssthen 	case '-':
710712b2f30Ssthen 		*res = x-y;
711712b2f30Ssthen 		break;
712712b2f30Ssthen 	case '/':
713712b2f30Ssthen 		*res = x/y;
714712b2f30Ssthen 		break;
715712b2f30Ssthen 	case '*':
716712b2f30Ssthen 		*res = x*y;
717712b2f30Ssthen 		break;
718712b2f30Ssthen 	default:
71966a34dc2Ssthen 		*res = 0;
720712b2f30Ssthen 		return 0;
721712b2f30Ssthen 	}
722712b2f30Ssthen 
723712b2f30Ssthen 	return 1;
724712b2f30Ssthen }
725712b2f30Ssthen 
726712b2f30Ssthen /** do macro arithmetic on two numbers and operand */
727712b2f30Ssthen static char*
do_macro_arith(char * orig,size_t remain,char ** arithstart)728712b2f30Ssthen do_macro_arith(char* orig, size_t remain, char** arithstart)
729712b2f30Ssthen {
730712b2f30Ssthen 	double x, y, result;
731712b2f30Ssthen 	char operator;
732712b2f30Ssthen 	int skip;
733712b2f30Ssthen 	char buf[32];
734712b2f30Ssthen 	char* at;
735712b2f30Ssthen 	/* not yet done? we want number operand number expanded first. */
736712b2f30Ssthen 	if(!*arithstart) {
737712b2f30Ssthen 		/* remember start pos of expr, skip the first number */
738712b2f30Ssthen 		at = orig;
739712b2f30Ssthen 		*arithstart = at;
740712b2f30Ssthen 		while(*at && (isdigit((unsigned char)*at) || *at == '.'))
741712b2f30Ssthen 			at++;
742712b2f30Ssthen 		return at;
743712b2f30Ssthen 	}
744712b2f30Ssthen 	/* move back to start */
745712b2f30Ssthen 	remain += (size_t)(orig - *arithstart);
746712b2f30Ssthen 	at = *arithstart;
747712b2f30Ssthen 
748712b2f30Ssthen 	/* parse operands */
749712b2f30Ssthen 	if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
750712b2f30Ssthen 		*arithstart = NULL;
751712b2f30Ssthen 		return do_macro_arith(orig, remain, arithstart);
752712b2f30Ssthen 	}
753712b2f30Ssthen 	if(isdigit((unsigned char)operator)) {
754712b2f30Ssthen 		*arithstart = orig;
755712b2f30Ssthen 		return at+skip; /* do nothing, but setup for later number */
756712b2f30Ssthen 	}
757712b2f30Ssthen 
758712b2f30Ssthen 	/* calculate result */
759712b2f30Ssthen 	if(!perform_arith(x, operator, y, &result)) {
760712b2f30Ssthen 		log_err("unknown operator: %s", at);
761712b2f30Ssthen 		return NULL;
762712b2f30Ssthen 	}
763712b2f30Ssthen 
764712b2f30Ssthen 	/* put result back in buffer */
765712b2f30Ssthen 	snprintf(buf, sizeof(buf), "%.12g", result);
766712b2f30Ssthen 	if(!do_buf_insert(at, remain, at+skip, buf))
767712b2f30Ssthen 		return NULL;
768712b2f30Ssthen 
769712b2f30Ssthen 	/* the result can be part of another expression, restart that */
770712b2f30Ssthen 	*arithstart = NULL;
771712b2f30Ssthen 	return at;
772712b2f30Ssthen }
773712b2f30Ssthen 
774712b2f30Ssthen /** Do range macro on expanded buffer */
775712b2f30Ssthen static char*
do_macro_range(char * buf)776712b2f30Ssthen do_macro_range(char* buf)
777712b2f30Ssthen {
778712b2f30Ssthen 	double x, y, z;
779712b2f30Ssthen 	if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
780712b2f30Ssthen 		log_err("range func requires 3 args: %s", buf);
781712b2f30Ssthen 		return NULL;
782712b2f30Ssthen 	}
783712b2f30Ssthen 	if(x <= y && y <= z) {
784712b2f30Ssthen 		char res[1024];
785712b2f30Ssthen 		snprintf(res, sizeof(res), "%.24g", y);
786712b2f30Ssthen 		return strdup(res);
787712b2f30Ssthen 	}
788712b2f30Ssthen 	fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
789712b2f30Ssthen 	return NULL;
790712b2f30Ssthen }
791712b2f30Ssthen 
792712b2f30Ssthen static char*
macro_expand(rbtree_type * store,struct replay_runtime * runtime,char ** text)793712b2f30Ssthen macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
794712b2f30Ssthen {
795712b2f30Ssthen 	char buf[10240];
796712b2f30Ssthen 	char* at = *text;
797712b2f30Ssthen 	size_t len = macro_length(at);
798712b2f30Ssthen 	int dofunc = 0;
799712b2f30Ssthen 	char* arithstart = NULL;
800712b2f30Ssthen 	if(len >= sizeof(buf))
801712b2f30Ssthen 		return NULL; /* too long */
802712b2f30Ssthen 	buf[0] = 0;
803712b2f30Ssthen 	(void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
804712b2f30Ssthen 	at = buf;
805712b2f30Ssthen 
806712b2f30Ssthen 	/* check for functions */
807712b2f30Ssthen 	if(strcmp(buf, "time") == 0) {
8088771e50fSsthen 		if(runtime)
809712b2f30Ssthen 			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
8108771e50fSsthen 		else
8118771e50fSsthen 			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)0);
812712b2f30Ssthen 		*text += len;
813712b2f30Ssthen 		return strdup(buf);
814712b2f30Ssthen 	} else if(strcmp(buf, "timeout") == 0) {
815712b2f30Ssthen 		time_t res = 0;
8168771e50fSsthen 		if(runtime) {
817712b2f30Ssthen 			struct fake_timer* t = first_timer(runtime);
818712b2f30Ssthen 			if(t && (time_t)t->tv.tv_sec >= runtime->now_secs)
819712b2f30Ssthen 				res = (time_t)t->tv.tv_sec - runtime->now_secs;
8208771e50fSsthen 		}
821712b2f30Ssthen 		snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
822712b2f30Ssthen 		*text += len;
823712b2f30Ssthen 		return strdup(buf);
824712b2f30Ssthen 	} else if(strncmp(buf, "ctime ", 6) == 0 ||
825712b2f30Ssthen 		strncmp(buf, "ctime\t", 6) == 0) {
826712b2f30Ssthen 		at += 6;
827712b2f30Ssthen 		dofunc = 1;
828712b2f30Ssthen 	} else if(strncmp(buf, "range ", 6) == 0 ||
829712b2f30Ssthen 		strncmp(buf, "range\t", 6) == 0) {
830712b2f30Ssthen 		at += 6;
831712b2f30Ssthen 		dofunc = 1;
832712b2f30Ssthen 	}
833712b2f30Ssthen 
834712b2f30Ssthen 	/* actual macro text expansion */
835712b2f30Ssthen 	while(*at) {
836712b2f30Ssthen 		size_t remain = sizeof(buf)-strlen(buf);
837712b2f30Ssthen 		if(strncmp(at, "${", 2) == 0) {
838712b2f30Ssthen 			at = do_macro_recursion(store, runtime, at, remain);
839712b2f30Ssthen 		} else if(*at == '$') {
840712b2f30Ssthen 			at = do_macro_variable(store, at, remain);
841712b2f30Ssthen 		} else if(isdigit((unsigned char)*at)) {
842712b2f30Ssthen 			at = do_macro_arith(at, remain, &arithstart);
843712b2f30Ssthen 		} else {
844712b2f30Ssthen 			/* copy until whitespace or operator */
845712b2f30Ssthen 			if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
846712b2f30Ssthen 				at++;
847712b2f30Ssthen 				while(*at && (isalnum((unsigned char)*at) || *at=='_'))
848712b2f30Ssthen 					at++;
849712b2f30Ssthen 			} else at++;
850712b2f30Ssthen 		}
851712b2f30Ssthen 		if(!at) return NULL; /* failure */
852712b2f30Ssthen 	}
853712b2f30Ssthen 	*text += len;
854712b2f30Ssthen 	if(dofunc) {
855712b2f30Ssthen 		/* post process functions, buf has the argument(s) */
856712b2f30Ssthen 		if(strncmp(buf, "ctime", 5) == 0) {
857712b2f30Ssthen 			return do_macro_ctime(buf+6);
858712b2f30Ssthen 		} else if(strncmp(buf, "range", 5) == 0) {
859712b2f30Ssthen 			return do_macro_range(buf+6);
860712b2f30Ssthen 		}
861712b2f30Ssthen 	}
862712b2f30Ssthen 	return strdup(buf);
863712b2f30Ssthen }
864712b2f30Ssthen 
865712b2f30Ssthen char*
macro_process(rbtree_type * store,struct replay_runtime * runtime,char * text)866712b2f30Ssthen macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
867712b2f30Ssthen {
868712b2f30Ssthen 	char buf[10240];
869712b2f30Ssthen 	char* next, *expand;
870712b2f30Ssthen 	char* at = text;
871712b2f30Ssthen 	if(!strstr(text, "${"))
872712b2f30Ssthen 		return strdup(text); /* no macros */
873712b2f30Ssthen 	buf[0] = 0;
874712b2f30Ssthen 	buf[sizeof(buf)-1]=0;
875712b2f30Ssthen 	while( (next=strstr(at, "${")) ) {
876712b2f30Ssthen 		/* copy text before next macro */
877712b2f30Ssthen 		if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
878712b2f30Ssthen 			return NULL; /* string too long */
879712b2f30Ssthen 		(void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
880712b2f30Ssthen 		/* process the macro itself */
881712b2f30Ssthen 		next += 2;
882712b2f30Ssthen 		expand = macro_expand(store, runtime, &next);
883712b2f30Ssthen 		if(!expand) return NULL; /* expansion failed */
884712b2f30Ssthen 		(void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
885712b2f30Ssthen 		free(expand);
886712b2f30Ssthen 		at = next;
887712b2f30Ssthen 	}
888712b2f30Ssthen 	/* copy remainder fixed text */
889712b2f30Ssthen 	(void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
890712b2f30Ssthen 	return strdup(buf);
891712b2f30Ssthen }
892712b2f30Ssthen 
893712b2f30Ssthen char*
macro_lookup(rbtree_type * store,char * name)894712b2f30Ssthen macro_lookup(rbtree_type* store, char* name)
895712b2f30Ssthen {
896712b2f30Ssthen 	struct replay_var* x = macro_getvar(store, name);
897712b2f30Ssthen 	if(!x) return strdup("");
898712b2f30Ssthen 	return strdup(x->value);
899712b2f30Ssthen }
900712b2f30Ssthen 
macro_print_debug(rbtree_type * store)901712b2f30Ssthen void macro_print_debug(rbtree_type* store)
902712b2f30Ssthen {
903712b2f30Ssthen 	struct replay_var* x;
904712b2f30Ssthen 	RBTREE_FOR(x, struct replay_var*, store) {
905712b2f30Ssthen 		log_info("%s = %s", x->name, x->value);
906712b2f30Ssthen 	}
907712b2f30Ssthen }
908712b2f30Ssthen 
909712b2f30Ssthen int
macro_assign(rbtree_type * store,char * name,char * value)910712b2f30Ssthen macro_assign(rbtree_type* store, char* name, char* value)
911712b2f30Ssthen {
912712b2f30Ssthen 	struct replay_var* x = macro_getvar(store, name);
913712b2f30Ssthen 	if(x) {
914712b2f30Ssthen 		free(x->value);
915712b2f30Ssthen 	} else {
916712b2f30Ssthen 		x = (struct replay_var*)malloc(sizeof(*x));
917712b2f30Ssthen 		if(!x) return 0;
918712b2f30Ssthen 		x->node.key = x;
919712b2f30Ssthen 		x->name = strdup(name);
920712b2f30Ssthen 		if(!x->name) {
921712b2f30Ssthen 			free(x);
922712b2f30Ssthen 			return 0;
923712b2f30Ssthen 		}
924712b2f30Ssthen 		(void)rbtree_insert(store, &x->node);
925712b2f30Ssthen 	}
926712b2f30Ssthen 	x->value = strdup(value);
927712b2f30Ssthen 	return x->value != NULL;
928712b2f30Ssthen }
929712b2f30Ssthen 
930712b2f30Ssthen /* testbound assert function for selftest.  counts the number of tests */
931712b2f30Ssthen #define tb_assert(x) \
932712b2f30Ssthen 	do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
933712b2f30Ssthen 		__FILE__, __LINE__, __func__, #x); \
934712b2f30Ssthen 		num_asserts++; \
935712b2f30Ssthen 	} while(0);
936712b2f30Ssthen 
testbound_selftest(void)937712b2f30Ssthen void testbound_selftest(void)
938712b2f30Ssthen {
939712b2f30Ssthen 	/* test the macro store */
940712b2f30Ssthen 	rbtree_type* store = macro_store_create();
941712b2f30Ssthen 	char* v;
942712b2f30Ssthen 	int r;
943712b2f30Ssthen 	int num_asserts = 0;
944712b2f30Ssthen 	tb_assert(store);
945712b2f30Ssthen 
946712b2f30Ssthen 	v = macro_lookup(store, "bla");
947712b2f30Ssthen 	tb_assert(strcmp(v, "") == 0);
948712b2f30Ssthen 	free(v);
949712b2f30Ssthen 
950712b2f30Ssthen 	v = macro_lookup(store, "vlerk");
951712b2f30Ssthen 	tb_assert(strcmp(v, "") == 0);
952712b2f30Ssthen 	free(v);
953712b2f30Ssthen 
954712b2f30Ssthen 	r = macro_assign(store, "bla", "waarde1");
955712b2f30Ssthen 	tb_assert(r);
956712b2f30Ssthen 
957712b2f30Ssthen 	v = macro_lookup(store, "vlerk");
958712b2f30Ssthen 	tb_assert(strcmp(v, "") == 0);
959712b2f30Ssthen 	free(v);
960712b2f30Ssthen 
961712b2f30Ssthen 	v = macro_lookup(store, "bla");
962712b2f30Ssthen 	tb_assert(strcmp(v, "waarde1") == 0);
963712b2f30Ssthen 	free(v);
964712b2f30Ssthen 
965712b2f30Ssthen 	r = macro_assign(store, "vlerk", "kanteel");
966712b2f30Ssthen 	tb_assert(r);
967712b2f30Ssthen 
968712b2f30Ssthen 	v = macro_lookup(store, "bla");
969712b2f30Ssthen 	tb_assert(strcmp(v, "waarde1") == 0);
970712b2f30Ssthen 	free(v);
971712b2f30Ssthen 
972712b2f30Ssthen 	v = macro_lookup(store, "vlerk");
973712b2f30Ssthen 	tb_assert(strcmp(v, "kanteel") == 0);
974712b2f30Ssthen 	free(v);
975712b2f30Ssthen 
976712b2f30Ssthen 	r = macro_assign(store, "bla", "ww");
977712b2f30Ssthen 	tb_assert(r);
978712b2f30Ssthen 
979712b2f30Ssthen 	v = macro_lookup(store, "bla");
980712b2f30Ssthen 	tb_assert(strcmp(v, "ww") == 0);
981712b2f30Ssthen 	free(v);
982712b2f30Ssthen 
983712b2f30Ssthen 	tb_assert( macro_length("}") == 1);
984712b2f30Ssthen 	tb_assert( macro_length("blabla}") == 7);
985712b2f30Ssthen 	tb_assert( macro_length("bla${zoink}bla}") == 7+8);
986712b2f30Ssthen 	tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
987712b2f30Ssthen 
988712b2f30Ssthen 	v = macro_process(store, NULL, "");
989712b2f30Ssthen 	tb_assert( v && strcmp(v, "") == 0);
990712b2f30Ssthen 	free(v);
991712b2f30Ssthen 
992712b2f30Ssthen 	v = macro_process(store, NULL, "${}");
993712b2f30Ssthen 	tb_assert( v && strcmp(v, "") == 0);
994712b2f30Ssthen 	free(v);
995712b2f30Ssthen 
996712b2f30Ssthen 	v = macro_process(store, NULL, "blabla ${} dinges");
997712b2f30Ssthen 	tb_assert( v && strcmp(v, "blabla  dinges") == 0);
998712b2f30Ssthen 	free(v);
999712b2f30Ssthen 
1000712b2f30Ssthen 	v = macro_process(store, NULL, "1${$bla}2${$bla}3");
1001712b2f30Ssthen 	tb_assert( v && strcmp(v, "1ww2ww3") == 0);
1002712b2f30Ssthen 	free(v);
1003712b2f30Ssthen 
1004712b2f30Ssthen 	v = macro_process(store, NULL, "it is ${ctime 123456}");
1005712b2f30Ssthen 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1006712b2f30Ssthen 	free(v);
1007712b2f30Ssthen 
1008712b2f30Ssthen 	r = macro_assign(store, "t1", "123456");
1009712b2f30Ssthen 	tb_assert(r);
1010712b2f30Ssthen 	v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
1011712b2f30Ssthen 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1012712b2f30Ssthen 	free(v);
1013712b2f30Ssthen 
1014712b2f30Ssthen 	v = macro_process(store, NULL, "it is ${ctime $t1}");
1015712b2f30Ssthen 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
1016712b2f30Ssthen 	free(v);
1017712b2f30Ssthen 
1018712b2f30Ssthen 	r = macro_assign(store, "x", "1");
1019712b2f30Ssthen 	tb_assert(r);
1020712b2f30Ssthen 	r = macro_assign(store, "y", "2");
1021712b2f30Ssthen 	tb_assert(r);
1022712b2f30Ssthen 	v = macro_process(store, NULL, "${$x + $x}");
1023712b2f30Ssthen 	tb_assert( v && strcmp(v, "2") == 0);
1024712b2f30Ssthen 	free(v);
1025712b2f30Ssthen 	v = macro_process(store, NULL, "${$x - $x}");
1026712b2f30Ssthen 	tb_assert( v && strcmp(v, "0") == 0);
1027712b2f30Ssthen 	free(v);
1028712b2f30Ssthen 	v = macro_process(store, NULL, "${$y * $y}");
1029712b2f30Ssthen 	tb_assert( v && strcmp(v, "4") == 0);
1030712b2f30Ssthen 	free(v);
1031712b2f30Ssthen 	v = macro_process(store, NULL, "${32 / $y + $x + $y}");
1032712b2f30Ssthen 	tb_assert( v && strcmp(v, "19") == 0);
1033712b2f30Ssthen 	free(v);
1034712b2f30Ssthen 
1035712b2f30Ssthen 	v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
1036712b2f30Ssthen 	tb_assert( v && strcmp(v, "108") == 0);
1037712b2f30Ssthen 	free(v);
1038712b2f30Ssthen 
1039712b2f30Ssthen 	v = macro_process(store, NULL, "${1 2 33 2 1}");
1040712b2f30Ssthen 	tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
1041712b2f30Ssthen 	free(v);
1042712b2f30Ssthen 
1043712b2f30Ssthen 	v = macro_process(store, NULL, "${123 3 + 5}");
1044712b2f30Ssthen 	tb_assert( v && strcmp(v, "123 8") == 0);
1045712b2f30Ssthen 	free(v);
1046712b2f30Ssthen 
1047712b2f30Ssthen 	v = macro_process(store, NULL, "${123 glug 3 + 5}");
1048712b2f30Ssthen 	tb_assert( v && strcmp(v, "123 glug 8") == 0);
1049712b2f30Ssthen 	free(v);
1050712b2f30Ssthen 
1051712b2f30Ssthen 	macro_store_delete(store);
1052712b2f30Ssthen 	printf("selftest successful (%d checks).\n", num_asserts);
1053712b2f30Ssthen }
1054