/*	$NetBSD: reduce.c,v 1.3.4.2 2024/02/29 11:39:21 martin Exp $	*/

/*
 * Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   PO Box 360
 *   Newmarket, NH 03857 USA
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: reduce.c,v 1.3.4.2 2024/02/29 11:39:21 martin Exp $");

#include "keama.h"

#include <sys/errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static struct element *reduce_equal_expression(struct element *left,
					       struct element *right);
static void debug(const char* fmt, ...);

/*
 * boolean_expression :== CHECK STRING |
 *                        NOT boolean-expression |
 *                        data-expression EQUAL data-expression |
 *                        data-expression BANG EQUAL data-expression |
 *                        data-expression REGEX_MATCH data-expression |
 *                        boolean-expression AND boolean-expression |
 *                        boolean-expression OR boolean-expression
 *                        EXISTS OPTION-NAME
 */

struct element *
reduce_boolean_expression(struct element *expr)
{
	/* trivial case: already done */
	if (expr->type == ELEMENT_BOOLEAN)
		return expr;

	/*
	 * From is_boolean_expression
	 */

	if (expr->type != ELEMENT_MAP)
		return NULL;

	/* check */
	if (mapContains(expr, "check"))
		/*
		 * syntax := { "check": <collection_name> }
		 * semantic: check_collection
		 *  on server try to match classes of the collection
		 */
		return NULL;


	/* exists */
	if (mapContains(expr, "exists")) {
		/*
		 * syntax := { "exists":
		 *             { "universe": <option_space_old>,
		 *               "name":  <option_name> }
		 *           }
		 * semantic: check universe/code from incoming packet
		 */
		struct element *arg;
		struct element *universe;
		struct element *name;
		struct option *option;
		char result[80];

		arg = mapGet(expr, "exists");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get exists argument");
			return NULL;
		}
		universe = mapGet(arg, "universe");
		if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
			debug("can't get exists option universe");
			return NULL;
		}
		name = mapGet(arg, "name");
		if ((name == NULL) || (name->type != ELEMENT_STRING)) {
			debug("can't get exists option name");
			return NULL;
		}
		option = option_lookup_name(stringValue(universe)->content,
					    stringValue(name)->content);
		if ((option == NULL) || (option->code == 0))
			return NULL;
		if (((local_family == AF_INET) &&
		     (strcmp(option->space->name, "dhcp4") != 0)) ||
		    ((local_family == AF_INET6) &&
		     (strcmp(option->space->name, "dhcp6") != 0)))
			return NULL;
		snprintf(result, sizeof(result),
			 "option[%u].exists", option->code);
		return createString(makeString(-1, result));
	}

	/* variable-exists */
	if (mapContains(expr, "variable-exists"))
		/*
		 * syntax := { "variable-exists": <variable_name> }
		 * semantics: find_binding(scope, name)
		 */
		return NULL;

	/* equal */
	if (mapContains(expr, "equal")) {
		/*
		 * syntax := { "equal":
		 *             { "left":  <expression>,
		 *               "right": <expression> }
		 *           }
		 * semantics: evaluate branches and return true
		 * if same type and same value
		 */
		struct element *arg;
		struct element *left;
		struct element *right;

		arg = mapGet(expr, "equal");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get equal argument");
			return NULL;
		}
		left = mapGet(arg, "left");
		if (left == NULL) {
			debug("can't get equal left branch");
			return NULL;
		}
		right = mapGet(arg, "right");
		if (right == NULL) {
			debug("can't get equal right branch");
			return NULL;
		}
		return reduce_equal_expression(left, right);
	}

	/* not-equal */
	if (mapContains(expr, "not-equal")) {
		/*
		 * syntax := { "not-equal":
		 *             { "left":  <expression>,
                 *               "right": <expression> }
                 *           }
                 * semantics: evaluate branches and return true
                 * if different type or different value
                 */
		struct element *arg;
		struct element *left;
		struct element *right;
		struct element *equal;
		struct string *result;

		arg = mapGet(expr, "not-equal");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get not-equal argument");
			return NULL;
		}
		left = mapGet(arg, "left");
		if (left == NULL) {
			debug("can't get not-equal left branch");
			return NULL;
		}
		right = mapGet(arg, "right");
		if (right == NULL) {
			debug("can't get not-equal right branch");
			return NULL;
		}
		equal = reduce_equal_expression(left, right);
		if ((equal == NULL) || (equal->type != ELEMENT_STRING))
			return NULL;
		result = makeString(-1, "not (");
		concatString(result, stringValue(equal));
		appendString(result, ")");
		return createString(result);
	}

	/* regex-match */
	if (mapContains(expr, "regex-match"))
		/*
		 * syntax := { "regex-match":
		 *             { "left":  <data_expression>,
		 *               "right": <data_expression> }
		 *           }
		 * semantics: evaluate branches, compile right as a
		 * regex and apply it to left
		 */
		return NULL;

	/* iregex-match */
	if (mapContains(expr, "iregex-match"))
		/*
		 * syntax := { "regex-match":
		 *             { "left":  <data_expression>,
		 *               "right": <data_expression> }
		 *           }
		 * semantics: evaluate branches, compile right as a
		 * case insensistive regex and apply it to left
		 */
		return NULL;

	/* and */
	if (mapContains(expr, "and")) {
		/*
		 * syntax := { "and":
		 *             { "left":  <boolean_expression>,
		 *               "right": <boolean_expression> }
		 *           }
		 * semantics: evaluate branches, return true
		 * if both are true
		 */
		struct element *arg;
		struct element *left;
		struct element *right;
		struct string *result;

		arg = mapGet(expr, "and");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get and argument");
			return NULL;
		}
		left = mapGet(arg, "left");
		if (left == NULL) {
			debug("can't get and left branch");
			return NULL;
		}
		right = mapGet(arg, "right");
		if (right == NULL) {
			debug("can't get and right branch");
			return NULL;
		}
		left = reduce_boolean_expression(left);
		if ((left == NULL) || (left->type != ELEMENT_STRING))
			return NULL;
		right = reduce_boolean_expression(right);
		if ((right == NULL) || (right->type != ELEMENT_STRING))
			return NULL;
		result = makeString(-1, "(");
		concatString(result, stringValue(left));
		appendString(result, ") and (");
		concatString(result, stringValue(right));
		appendString(result, ")");
		return createString(result);
	}

	/* or */
	if (mapContains(expr, "or")) {
		/*
		 * syntax := { "or":
		 *             { "left":  <boolean_expression>,
		 *               "right": <boolean_expression> }
		 *           }
		 * semantics: evaluate branches, return true
		 * if any is true
		 */
		struct element *arg;
		struct element *left;
		struct element *right;
		struct string *result;

		arg = mapGet(expr, "or");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get or argument");
			return NULL;
		}
		left = mapGet(arg, "left");
		if (left == NULL) {
			debug("can't get or left branch");
			return NULL;
		}
		right = mapGet(arg, "right");
		if (right == NULL) {
			debug("can't get or right branch");
			return NULL;
		}
		left = reduce_boolean_expression(left);
		if ((left == NULL) || (left->type != ELEMENT_STRING))
			return NULL;
		right = reduce_boolean_expression(right);
		if ((right == NULL) || (right->type != ELEMENT_STRING))
			return NULL;
		result = makeString(-1, "(");
		concatString(result, stringValue(left));
		appendString(result, ") or (");
		concatString(result, stringValue(right));
		appendString(result, ")");
		return createString(result);
	}

	/* not */
	if (mapContains(expr, "not")) {
		/*
		 * syntax := { "not": <boolean_expression> }
		 * semantic: evaluate its branch and return its negation
		 */
		struct element *arg;
		struct string *result;

		arg = mapGet(expr, "not");
		if (arg == NULL) {
			debug("can't get not argument");
			return NULL;
		}
		arg = reduce_boolean_expression(arg);
		if ((arg == NULL) || (arg->type != ELEMENT_STRING))
			return NULL;
		result = makeString(-1, "not (");
		concatString(result, stringValue(arg));
		appendString(result, ")");
		return createString(result);
	}

	/* known */
	if (mapContains(expr, "known"))
		/*
		 * syntax := { "known": null }
		 * semantics: client is known, i.e., has a matching
		 * host declaration (aka reservation in Kea)
		 */
		return NULL;

	/* static */
	if (mapContains(expr, "static"))
		/*
		 * syntax := { "static": null }
		 * semantics: lease is static (doesn't exist in Kea)
		 */
		return NULL;

	return NULL;
}

/*
 * data_expression :== SUBSTRING LPAREN data-expression COMMA
 *                                      numeric-expression COMMA
 *                                      numeric-expression RPAREN |
 *                     CONCAT LPAREN data-expression COMMA
 *                                      data-expression RPAREN
 *                     SUFFIX LPAREN data_expression COMMA
 *                                   numeric-expression RPAREN |
 *                     LCASE LPAREN data_expression RPAREN |
 *                     UCASE LPAREN data_expression RPAREN |
 *                     OPTION option_name |
 *                     HARDWARE |
 *                     PACKET LPAREN numeric-expression COMMA
 *                                   numeric-expression RPAREN |
 *                     V6RELAY LPAREN numeric-expression COMMA
 *                                    data-expression RPAREN |
 *                     STRING |
 *                     colon_separated_hex_list
 */

struct element *
reduce_data_expression(struct element *expr)
{
	/* trivial case: already done */
	if (expr->type == ELEMENT_STRING)
		return expr;

	/*
	 * From is_data_expression
	 */

	if (expr->type != ELEMENT_MAP)
		return NULL;

	/* substring */
	if (mapContains(expr, "substring")) {
		/*
		 * syntax := { "substring":
		 *             { "expression": <data_expression>,
		 *               "offset":     <numeric_expression>,
		 *               "length":     <numeric_expression> }
		 *           }
		 * semantic: evaluate arguments, if the string is
		 * shorter than offset return "" else return substring
		 */
		struct element *arg;
		struct element *string;
		struct element *offset;
		struct element *length;
		struct string *result;
		int64_t off;
		int64_t len;
		char buf[80];

		arg = mapGet(expr, "substring");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get substring argument");
			return NULL;
		}
		string = mapGet(arg, "expression");
		if (string == NULL) {
			debug("can't get substring expression");
			return NULL;
		}
		offset = mapGet(arg, "offset");
		if (offset  == NULL) {
			debug("can't get substring offset");
			return NULL;
		}
		length = mapGet(arg, "length");
		if (length  == NULL) {
			debug("can't get substring length");
			return NULL;
		}
		/* can't be a literal as it was evaluated before */
		string = reduce_data_expression(string);
		if ((string == NULL) || (string->type != ELEMENT_STRING))
			return NULL;
		offset = reduce_numeric_expression(offset);
		if ((offset == NULL) || (offset->type != ELEMENT_INTEGER))
			return NULL;
		off = intValue(offset);
		if (off < 0) {
			debug("substring with a negative offset (%lld)",
			      (long long)off);
			return NULL;
		}
		length = reduce_numeric_expression(length);
		if ((length == NULL) || (length->type != ELEMENT_INTEGER))
			return NULL;
		len = intValue(length);
		if (len < 0) {
			debug("substring with a negative length (%lld)",
			      (long long)len);
			return NULL;
		}
		result = makeString(-1, "substring(");
		concatString(result, stringValue(string));
		snprintf(buf, sizeof(buf),
			 ",%u,%u)", (unsigned)off, (unsigned)len);
		appendString(result, buf);
		return createString(result);
	}

	/* suffix */
	if (mapContains(expr, "suffix")) {
		/*
		 * syntax := { "suffix":
		 *             { "expression": <data_expression>,
		 *               "length":     <numeric_expression> }
		 *           }
		 * semantic: evaluate arguments, if the string is
		 * shorter than length return it else return suffix
		 */
		struct element *arg;
		struct element *string;
		struct element *length;
		struct string *result;
		int64_t len;
		char buf[80];

		arg = mapGet(expr, "suffix");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get suffix argument");
			return NULL;
		}
		string = mapGet(arg, "expression");
		if (string == NULL) {
			debug("can't get suffix expression");
			return NULL;
		}
		length = mapGet(arg, "length");
		if (length  == NULL) {
			debug("can't get suffix length");
			return NULL;
		}
		/* can't be a literal as it was evaluated before */
		string = reduce_data_expression(string);
		if ((string == NULL) || (string->type != ELEMENT_STRING))
			return NULL;
		length = reduce_numeric_expression(length);
		if ((length == NULL) || (length->type != ELEMENT_INTEGER))
			return NULL;
		len = intValue(length);
		if (len < 0) {
			debug("suffix with a negative length (%lld)",
			      (long long)len);
			return NULL;
		}
		result = makeString(-1, "substring(");
		concatString(result, stringValue(string));
		snprintf(buf, sizeof(buf), ",-%u,all)", (unsigned)len);
		appendString(result, buf);
		return createString(result);
	}

	/* lowercase */
	if (mapContains(expr, "lowercase"))
		/*
		 * syntax := { "lowercase": <data_expression> }
		 * semantic: evaluate its argument and apply tolower to
		 * its content
		 */
		return NULL;

	/* uppercase */
	if (mapContains(expr, "uppercase"))
		/*
		 * syntax := { "uppercase": <data_expression> }
		 * semantic: evaluate its argument and apply toupper to
		 * its content
		 */
		return NULL;

	/* option */
	if (mapContains(expr, "option")) {
		/*
		 * syntax := { "option":
		 *             { "universe": <option_space_old>,
		 *               "name":  <option_name> }
		 *           }
		 * semantic: get universe/code option from incoming packet
		 */
		struct element *arg;
		struct element *universe;
		struct element *name;
		struct option *option;
		char result[80];

		arg = mapGet(expr, "option");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get option argument");
			return NULL;
		}
		universe = mapGet(arg, "universe");
		if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
			debug("can't get option universe");
			return NULL;
		}
		name = mapGet(arg, "name");
		if ((name == NULL) || (name->type != ELEMENT_STRING)) {
			debug("can't get option name");
			return NULL;
		}
		option = option_lookup_name(stringValue(universe)->content,
					    stringValue(name)->content);
		if ((option == NULL) || (option->code == 0))
			return NULL;
		if (((local_family == AF_INET) &&
		     (strcmp(option->space->name, "dhcp4") != 0)) ||
		    ((local_family == AF_INET6) &&
		     (strcmp(option->space->name, "dhcp6") != 0)))
			return NULL;
		snprintf(result, sizeof(result),
			 "option[%u].hex", option->code);
		return createString(makeString(-1, result));
	}

	/* hardware */
	if (mapContains(expr, "hardware")) {
		/*
		 * syntax := { "hardware": null }
		 * semantic: get mac type and address from incoming packet
		 */
		struct string *result;

		if (local_family != AF_INET) {
			debug("get hardware for DHCPv6");
			return NULL;
		}
		result = makeString(-1,
			    "concat(substring(pkt4.htype,-1,all),pkt4.mac)");
		return createString(result);
	}

	/* hw-type */
	if (mapContains(expr, "hw-type")) {
		/*
		 * ADDED
		 * syntax := { "hw-type": null }
		 * semantic: get mac type from incoming packet
		 */
		struct string *result;

		if (local_family != AF_INET) {
			debug("get hw-type for DHCPv6");
			return NULL;
		}
		result = makeString(-1, "substring(pkt4.htype,-1,all)");
		return createString(result);
	}

	/* hw-address */
	if (mapContains(expr, "hw-address")) {
		/*
		 * ADDED
		 * syntax := { "hw-address": null }
		 * semantic: get mac address from incoming packet
		 */
		struct string *result;

		if (local_family != AF_INET) {
			debug("get hw-address for DHCPv6");
			return NULL;
		}
		result = makeString(-1, "pkt4.mac");
		return createString(result);
	}

	/* const-data */
	if (mapContains(expr, "const-data")) {
		/*
		 * syntax := { "const-data": <string> }
		 * semantic: embedded string value
		 */
		struct element *arg;

		arg = mapGet(expr, "const-data");
		if ((arg == NULL) || (arg->type != ELEMENT_STRING)) {
			debug("can't get const-data argument");
			return NULL;
		}
		return createString(stringValue(arg));
	}

	/* packet */
	if (mapContains(expr, "packet"))
		/*
		 * syntax := { "packet":
		 *             { "offset": <numeric_expression>,
		 *               "length": <numeric_expression> }
		 *           }
		 * semantic: return the selected substring of the incoming
		 * packet content
		 */
		return NULL;

	/* concat */
	if (mapContains(expr, "concat")) {
		/*
		 * syntax := { "concat":
		 *             { "left":  <data_expression>,
		 *               "right": <data_expression> }
		 *           }
		 * semantic: evaluate arguments and return the concatenation
		 */
		struct element *arg;
		struct element *left;
		struct element *right;
		struct string *result;

		arg = mapGet(expr, "concat");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get concat argument");
			return NULL;
		}
		left = mapGet(arg, "left");
		if (left == NULL) {
			debug("can't get concat left branch");
			return NULL;
		}
		right = mapGet(arg, "right");
		if (right == NULL) {
			debug("can't get concat right branch");
			return NULL;
		}
		/* left is a literal case */
		if (left->type == ELEMENT_STRING) {
			/* can't be a literal as it was evaluated before */
			right = reduce_data_expression(right);
			if ((right == NULL) || (right->type != ELEMENT_STRING))
				return NULL;
			result = makeString(-1, "concat(");
			concatString(result, quote(stringValue(left)));
			appendString(result, ", ");
			concatString(result, stringValue(right));
			appendString(result, ")");
			return createString(result);
		}
		left = reduce_data_expression(left);
		if ((left == NULL) || (left->type != ELEMENT_STRING))
			return NULL;
		/* right is a literal case */
		if (right->type == ELEMENT_STRING) {
			/* literal left was handled before */
			result = makeString(-1, "concat(");
			concatString(result, stringValue(left));
			appendString(result, ", ");
			concatString(result, quote(stringValue(right)));
			appendString(result, ")");
			return createString(result);
		}
		right = reduce_data_expression(right);
		if ((right == NULL) || (right->type != ELEMENT_STRING))
			return NULL;
		result = makeString(-1, "concat(");
		concatString(result, stringValue(left));
		appendString(result, ", ");
		concatString(result, stringValue(right));
		appendString(result, ")");
		return createString(result);
	}

	/* encapsulate */
	if (mapContains(expr, "encapsulate"))
		/*
		 * syntax := { "encapsulate": <encapsulated_space> }
		 * semantic: encapsulate options of the given space
		 */
		return NULL;

	/* encode-int8 */
	if (mapContains(expr, "encode-int8"))
		/*
		 * syntax := { "encode-int8": <numeric_expression> }
		 * semantic: return a string buffer with the evaluated
		 * number as content
		 */
		return NULL;

	/* encode-int16 */
	if (mapContains(expr, "encode-int16"))
		/*
		 * syntax := { "encode-int16": <numeric_expression> }
		 * semantic: return a string buffer with the evaluated
		 * number as content
		 */
		return NULL;

	/* encode-int32 */
	if (mapContains(expr, "encode-int32"))
		/*
		 * syntax := { "encode-int32": <numeric_expression> }
		 * semantic: return a string buffer with the evaluated
		 * number as content
		 */
		return NULL;

	/* gethostbyname */
	if (mapContains(expr, "gethostbyname"))
		/*
		 * syntax := { "gethostbyname": <string> }
		 * semantic: call gethostbyname and return
		 * a binary buffer with addresses
		 */
		return NULL;

	/* binary-to-ascii */
	if (mapContains(expr, "binary-to-ascii"))
		/*
		 * syntax := { "binary-to-ascii":
		 *             { "base":      <numeric_expression 2..16>,
		 *               "width":     <numeric_expression 8, 16 or 32>,
		 *               "separator": <data_expression>,
		 *               "buffer":    <data_expression> }
		 *           }
		 * semantic: split the input buffer into int8/16/32 numbers,
		 * output them separated by the given string
		 */
		return NULL;

	/* filename */
	if (mapContains(expr, "filename"))
		/*
		 * syntax := { "filename": null }
		 * semantic: get filename field from incoming DHCPv4 packet
		 */
		return NULL;

	/* server-name */
	if (mapContains(expr, "server-name"))
		/*
		 * syntax := { "server-name": null }
		 * semantic: get server-name field from incoming DHCPv4 packet
		 */
		return NULL;

	/* reverse */
	if (mapContains(expr, "reverse"))
		/*
		 * syntax := { "reverse":
		 *             { "width": <numeric_expression>,
		 *               "buffer":    <data_expression> }
		 *           }
		 * semantic: reverse the input buffer by width chunks of bytes
		 */
		return NULL;

	/* pick-first-value */
	if (mapContains(expr, "pick-first-value"))
		/*
		 * syntax := { "pick-first-value":
		 *             [ <data_expression>, ... ]
		 *           }
		 * semantic: evaluates expressions and return the first
		 * not null, return null if all are null
		 */
		return NULL;

	/* host-decl-name */
	if (mapContains(expr, "host-decl-name"))
		/*
		 * syntax := { "host-decl-name": null }
		 * semantic: return the name of the matching host
		 * declaration (aka revervation in kea) or null
		 */
		return NULL;

	/* leased-address */
	if (mapContains(expr, "leased-address"))
		/*
		 * syntax := { "leased-address": null }
		 * semantic: return the address of the assigned lease or
		 * log a message
		 */
		return NULL;

	/* config-option */
	if (mapContains(expr, "config-option"))
		/*
		 * syntax := { "config-option":
		 *             { "universe": <option_space_old>,
		 *               "name":  <option_name> }
		 *           }
		 * semantic: get universe/code option to send
		 */
		return NULL;

	/* null */
	if (mapContains(expr, "null")) {
		/*
		 * syntax := { "null": null }
		 * semantic: return null
		 */
		debug("unexpected null: this expression was not evaluated");
		return NULL;
	}

	/* gethostname */
	if (mapContains(expr, "gethostname")) {
		/*
		 * syntax := { "gethostname": null }
		 * semantic: return gethostname
		 */
		debug("unexpected gethostname: this expression was not "
		      "evaluated");
		return NULL;
	}

	/* v6relay */
	if (mapContains(expr, "v6relay")) {
		/*
		 * syntax := { "v6relay":
		 *             { "relay": <numeric_expression>,
		 *               "relay-option" <data_expression> }
		 *           }
		 * semantic: relay is a counter from client, 0 is no-op,
		 * 1 is the relay closest to the client, etc, option
		 * is a dhcp6 option ans is return when found
		 */
		struct element *arg;
		struct element *relay;
		struct element *universe;
		struct element *name;
		struct option *option;
		int64_t r;
		char result[100];

		if (local_family != AF_INET6) {
			debug("get v6relay for DHCPv4");
			return NULL;
		}
		arg = mapGet(expr, "v6relay");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get v6relay argument");
			return NULL;
		}
		relay = mapGet(arg, "relay");
		if (relay == NULL) {
			debug("can't get v6relay relay");
			return NULL;
		}
		relay = reduce_numeric_expression(relay);
		if ((relay == NULL) || (relay->type != ELEMENT_INTEGER))
			return NULL;
		r = intValue(relay);
		if (r < 0) {
			debug("v6relay called with illegal relay (%lld)",
			      (long long)r);
			return NULL;
		}
		arg = mapGet(arg, "relay-option");
		if ((arg == NULL) || (arg->type != ELEMENT_MAP)) {
			debug("can't get v6relay relay-option");
			return NULL;
		}
		universe = mapGet(arg, "universe");
		if ((universe == NULL) || (universe->type != ELEMENT_STRING)) {
			debug("can't get v6relay option universe");
			NULL;
		}
		name = mapGet(arg, "name");
		if ((name == NULL) || (name->type != ELEMENT_STRING)) {
			debug("can't get v6relay option name");
			return NULL;
		}
		option = option_lookup_name(stringValue(universe)->content,
					    stringValue(name)->content);
		if ((option == NULL) || (option->code == 0) ||
		    (strcmp(option->space->name, "dhcp6") != 0))
			return NULL;
		if (r == 0)
			snprintf(result, sizeof(result),
				 "option[%u].hex", option->code);
		else {
			/* r > MAX_V6RELAY_HOPS means the relay closest
			   to server */
			if (r > MAX_V6RELAY_HOPS)
				r = 0;
			/* Kea counts from the server, use negative nesting
			   levels to count from the client */
			snprintf(result, sizeof(result),
				 "relay6[%d].option[%u].hex",
				 (int)-r, option->code);
		}
		return createString(makeString(-1, result));
	}

	return NULL;
}

struct element *
reduce_numeric_expression(struct element *expr)
{
	/* trivial case: already done */
	if (expr->type == ELEMENT_INTEGER)
		return expr;

	if (expr->type != ELEMENT_MAP)
		return NULL;

	/* Kea has no numeric operators... */
	return NULL;
}

static struct element *
reduce_equal_expression(struct element *left, struct element *right)
{
	struct string *result;

	/*
	 * numeric case was handled by evaluation
	 */

	if (!is_data_expression(left) || !is_data_expression(right))
		return NULL;

	/* left is a literal case */
	if (left->type == ELEMENT_STRING) {
		/* can't be a literal as it was evaluated before */
		right = reduce_data_expression(right);
		if ((right == NULL) || (right->type != ELEMENT_STRING))
			return NULL;
		result = allocString();
		concatString(result, quote(stringValue(left)));
		appendString(result, " == ");
		concatString(result, stringValue(right));
		return createString(result);
	}
	left = reduce_data_expression(left);
	if ((left == NULL) || (left->type != ELEMENT_STRING))
		return NULL;

	/* right is a literal case */
	if (right->type == ELEMENT_STRING) {
		/* literal left was handled before */
		result = allocString();
		concatString(result, stringValue(left));
		appendString(result, " == ");
		concatString(result, quote(stringValue(right)));
		return createString(result);
	}
	right = reduce_data_expression(right);
	if ((right == NULL) || (right->type != ELEMENT_STRING))
		return NULL;

	result = allocString();
	concatString(result, stringValue(left));
	appendString(result, " == ");
	concatString(result, stringValue(right));
	return createString(result);
}

static void
debug(const char* fmt, ...)
{
	va_list list;

	va_start(list, fmt);
	vfprintf(stderr, fmt, list);
	fprintf(stderr, "\n");
	va_end(list);
}