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