/*
* testcode/replay.c - store and use a replay of events for the DNS resolver.
*
* Copyright (c) 2007, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
* Store and use a replay of events for the DNS resolver.
* Used to test known scenarios to get known outcomes.
*/
/** max length of lines in file */
#define MAX_LINE_LEN 10240
/**
* Expand a macro
* @param store: value storage
* @param runtime: replay runtime for other stuff.
* @param text: the macro text, after the ${, Updated to after the } when
* done (successfully).
* @return expanded text, malloced. NULL on failure.
*/
static char* macro_expand(rbtree_type* store,
struct replay_runtime* runtime, char** text);
/** parse keyword in string.
* @param line: if found, the line is advanced to after the keyword.
* @param keyword: string.
* @return: true if found, false if not.
*/
static int
parse_keyword(char** line, const char* keyword)
{
size_t len = (size_t)strlen(keyword);
if(strncmp(*line, keyword, len) == 0) {
*line += len;
return 1;
}
return 0;
}
int
replay_var_compare(const void* a, const void* b)
{
struct replay_var* x = (struct replay_var*)a;
struct replay_var* y = (struct replay_var*)b;
return strcmp(x->name, y->name);
}
/** perform arithmetic operator */
static double
perform_arith(double x, char op, double y, double* res)
{
switch(op) {
case '+':
*res = x+y;
break;
case '-':
*res = x-y;
break;
case '/':
*res = x/y;
break;
case '*':
*res = x*y;
break;
default:
*res = 0;
return 0;
}
return 1;
}
/** do macro arithmetic on two numbers and operand */
static char*
do_macro_arith(char* orig, size_t remain, char** arithstart)
{
double x, y, result;
char operator;
int skip;
char buf[32];
char* at;
/* not yet done? we want number operand number expanded first. */
if(!*arithstart) {
/* remember start pos of expr, skip the first number */
at = orig;
*arithstart = at;
while(*at && (isdigit((unsigned char)*at) || *at == '.'))
at++;
return at;
}
/* move back to start */
remain += (size_t)(orig - *arithstart);
at = *arithstart;
/* parse operands */
if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
*arithstart = NULL;
return do_macro_arith(orig, remain, arithstart);
}
if(isdigit((unsigned char)operator)) {
*arithstart = orig;
return at+skip; /* do nothing, but setup for later number */
}
/* calculate result */
if(!perform_arith(x, operator, y, &result)) {
log_err("unknown operator: %s", at);
return NULL;
}
/* put result back in buffer */
snprintf(buf, sizeof(buf), "%.12g", result);
if(!do_buf_insert(at, remain, at+skip, buf))
return NULL;
/* the result can be part of another expression, restart that */
*arithstart = NULL;
return at;
}
/** Do range macro on expanded buffer */
static char*
do_macro_range(char* buf)
{
double x, y, z;
if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
log_err("range func requires 3 args: %s", buf);
return NULL;
}
if(x <= y && y <= z) {
char res[1024];
snprintf(res, sizeof(res), "%.24g", y);
return strdup(res);
}
fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
return NULL;
}
static char*
macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
{
char buf[10240];
char* at = *text;
size_t len = macro_length(at);
int dofunc = 0;
char* arithstart = NULL;
if(len >= sizeof(buf))
return NULL; /* too long */
buf[0] = 0;
(void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
at = buf;
/* testbound assert function for selftest. counts the number of tests */
#define tb_assert(x) \
do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
__FILE__, __LINE__, __func__, #x); \
num_asserts++; \
} while(0);
void testbound_selftest(void)
{
/* test the macro store */
rbtree_type* store = macro_store_create();
char* v;
int r;
int num_asserts = 0;
tb_assert(store);
v = macro_lookup(store, "bla");
tb_assert(strcmp(v, "") == 0);
free(v);
v = macro_lookup(store, "vlerk");
tb_assert(strcmp(v, "") == 0);
free(v);
r = macro_assign(store, "bla", "waarde1");
tb_assert(r);
v = macro_lookup(store, "vlerk");
tb_assert(strcmp(v, "") == 0);
free(v);
v = macro_lookup(store, "bla");
tb_assert(strcmp(v, "waarde1") == 0);
free(v);
r = macro_assign(store, "vlerk", "kanteel");
tb_assert(r);
v = macro_lookup(store, "bla");
tb_assert(strcmp(v, "waarde1") == 0);
free(v);
v = macro_lookup(store, "vlerk");
tb_assert(strcmp(v, "kanteel") == 0);
free(v);
r = macro_assign(store, "bla", "ww");
tb_assert(r);
v = macro_lookup(store, "bla");
tb_assert(strcmp(v, "ww") == 0);
free(v);
v = macro_process(store, NULL, "");
tb_assert( v && strcmp(v, "") == 0);
free(v);
v = macro_process(store, NULL, "${}");
tb_assert( v && strcmp(v, "") == 0);
free(v);
v = macro_process(store, NULL, "blabla ${} dinges");
tb_assert( v && strcmp(v, "blabla dinges") == 0);
free(v);
v = macro_process(store, NULL, "1${$bla}2${$bla}3");
tb_assert( v && strcmp(v, "1ww2ww3") == 0);
free(v);
v = macro_process(store, NULL, "it is ${ctime 123456}");
tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
free(v);
r = macro_assign(store, "t1", "123456");
tb_assert(r);
v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
free(v);
v = macro_process(store, NULL, "it is ${ctime $t1}");
tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
free(v);
r = macro_assign(store, "x", "1");
tb_assert(r);
r = macro_assign(store, "y", "2");
tb_assert(r);
v = macro_process(store, NULL, "${$x + $x}");
tb_assert( v && strcmp(v, "2") == 0);
free(v);
v = macro_process(store, NULL, "${$x - $x}");
tb_assert( v && strcmp(v, "0") == 0);
free(v);
v = macro_process(store, NULL, "${$y * $y}");
tb_assert( v && strcmp(v, "4") == 0);
free(v);
v = macro_process(store, NULL, "${32 / $y + $x + $y}");
tb_assert( v && strcmp(v, "19") == 0);
free(v);
v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
tb_assert( v && strcmp(v, "108") == 0);
free(v);