From e0840f11b05204c318a894df983e7ab20a3597da Mon Sep 17 00:00:00 2001
From: Ben Pfaff
Date: Wed, 15 Apr 2015 16:49:50 -0700
Subject: [PATCH] expr: New module for Boolean expressions on fields, for use
in OVN.
Known weaknesses:
* String fields can't be converted to flows yet. A subsequent commit
will fix this.
* Flows aren't optimal in some situations likely to be common.
I don't think either of those is a reason not to commit this;
this is a solid base to build on.
Signed-off-by: Ben Pfaff
Acked-by: Russell Bryant
---
ovn/TODO | 25 -
ovn/lib/automake.mk | 6 +-
ovn/lib/expr.c | 2351 +++++++++++++++++++++++++++++++++++++++++++
ovn/lib/expr.h | 367 +++++++
ovn/ovn-sb.xml | 353 +++++--
tests/ovn.at | 253 +++++
tests/test-ovn.c | 1176 ++++++++++++++++++++++
7 files changed, 4403 insertions(+), 128 deletions(-)
create mode 100644 ovn/lib/expr.c
create mode 100644 ovn/lib/expr.h
diff --git a/ovn/TODO b/ovn/TODO
index f5ff888a9..3b5d97e50 100644
--- a/ovn/TODO
+++ b/ovn/TODO
@@ -4,31 +4,6 @@
the same syntax and I imagine the same code ought to be useful in
ovn-nbd for ACL match expressions.
-** Definition of data structures to represent a match expression as a
- syntax tree.
-
-** Definition of data structures to represent variables (fields).
-
- Fields need names and prerequisites. Most fields are numeric and
- thus need widths. We need also need a way to represent nominal
- fields (currently just logical port names). It might be
- appropriate to associate fields directly with OXM/NXM code points;
- we have to decide whether we want OVN to use the OVS flow structure
- or work with OXM more directly.
-
- Probably should be defined so that the data structure is also
- useful for references to fields in action parsing.
-
-** Parsing into syntax tree.
-
-** Semantic checking against variable definitions.
-
-** Applying prerequisites.
-
-** Simplification into conjunction-of-disjunctions (CoD) form.
-
-** Transformation from CoD form into OXM matches.
-
* ovn-controller
** Flow table handling in ovn-controller.
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index 9b5351d1e..91a4fe871 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -1,2 +1,6 @@
lib_LTLIBRARIES += ovn/lib/libovn.la
-ovn_lib_libovn_la_SOURCES = ovn/lib/lex.c ovn/lib/lex.h
+ovn_lib_libovn_la_SOURCES = \
+ ovn/lib/expr.c \
+ ovn/lib/expr.h \
+ ovn/lib/lex.c \
+ ovn/lib/lex.h
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
new file mode 100644
index 000000000..e648dbd1b
--- /dev/null
+++ b/ovn/lib/expr.c
@@ -0,0 +1,2351 @@
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include "expr.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "lex.h"
+#include "match.h"
+#include "shash.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(expr);
+
+/* Returns the name of measurement level 'level'. */
+const char *
+expr_level_to_string(enum expr_level level)
+{
+ switch (level) {
+ case EXPR_L_NOMINAL: return "nominal";
+ case EXPR_L_BOOLEAN: return "Boolean";
+ case EXPR_L_ORDINAL: return "ordinal";
+ default: OVS_NOT_REACHED();
+ }
+}
+
+/* Relational operators. */
+
+/* Returns a string form of relational operator 'relop'. */
+const char *
+expr_relop_to_string(enum expr_relop relop)
+{
+ switch (relop) {
+ case EXPR_R_EQ: return "==";
+ case EXPR_R_NE: return "!=";
+ case EXPR_R_LT: return "<";
+ case EXPR_R_LE: return "<=";
+ case EXPR_R_GT: return ">";
+ case EXPR_R_GE: return ">=";
+ default: OVS_NOT_REACHED();
+ }
+}
+
+bool
+expr_relop_from_token(enum lex_type type, enum expr_relop *relop)
+{
+ enum expr_relop r;
+
+ switch ((int) type) {
+ case LEX_T_EQ: r = EXPR_R_EQ; break;
+ case LEX_T_NE: r = EXPR_R_NE; break;
+ case LEX_T_LT: r = EXPR_R_LT; break;
+ case LEX_T_LE: r = EXPR_R_LE; break;
+ case LEX_T_GT: r = EXPR_R_GT; break;
+ case LEX_T_GE: r = EXPR_R_GE; break;
+ default: return false;
+ }
+
+ if (relop) {
+ *relop = r;
+ }
+ return true;
+}
+
+/* Returns the relational operator that 'relop' becomes if you turn the
+ * relation's operands around, e.g. EXPR_R_EQ does not change because "a == b"
+ * and "b == a" are equivalent, but EXPR_R_LE becomes EXPR_R_GE because "a <=
+ * b" is equivalent to "b >= a". */
+static enum expr_relop
+expr_relop_turn(enum expr_relop relop)
+{
+ switch (relop) {
+ case EXPR_R_EQ: return EXPR_R_EQ;
+ case EXPR_R_NE: return EXPR_R_NE;
+ case EXPR_R_LT: return EXPR_R_GT;
+ case EXPR_R_LE: return EXPR_R_GE;
+ case EXPR_R_GT: return EXPR_R_LT;
+ case EXPR_R_GE: return EXPR_R_LE;
+ default: OVS_NOT_REACHED();
+ }
+}
+
+/* Returns the relational operator that is the opposite of 'relop'. */
+static enum expr_relop
+expr_relop_invert(enum expr_relop relop)
+{
+ switch (relop) {
+ case EXPR_R_EQ: return EXPR_R_NE;
+ case EXPR_R_NE: return EXPR_R_EQ;
+ case EXPR_R_LT: return EXPR_R_GE;
+ case EXPR_R_LE: return EXPR_R_GT;
+ case EXPR_R_GT: return EXPR_R_LE;
+ case EXPR_R_GE: return EXPR_R_LT;
+ default: OVS_NOT_REACHED();
+ }
+}
+
+/* Constructing and manipulating expressions. */
+
+/* Creates and returns a logical AND or OR expression (according to 'type',
+ * which must be EXPR_T_AND or EXPR_T_OR) that initially has no
+ * sub-expressions. (To satisfy the invariants for expressions, the caller
+ * must add at least two sub-expressions whose types are different from
+ * 'type'.) */
+struct expr *
+expr_create_andor(enum expr_type type)
+{
+ struct expr *e = xmalloc(sizeof *e);
+ e->type = type;
+ list_init(&e->andor);
+ return e;
+}
+
+/* Returns a logical AND or OR expression (according to 'type', which must be
+ * EXPR_T_AND or EXPR_T_OR) whose sub-expressions are 'a' and 'b', with some
+ * flexibility:
+ *
+ * - If 'a' or 'b' is NULL, just returns the other one (which means that if
+ * that other one is not of the given 'type', then the returned
+ * expression is not either).
+ *
+ * - If 'a' or 'b', or both, have type 'type', then they are combined into
+ * a single node that satisfies the invariants for expressions. */
+struct expr *
+expr_combine(enum expr_type type, struct expr *a, struct expr *b)
+{
+ if (!a) {
+ return b;
+ } else if (!b) {
+ return a;
+ } else if (a->type == type) {
+ if (b->type == type) {
+ list_splice(&a->andor, b->andor.next, &b->andor);
+ free(b);
+ } else {
+ list_push_back(&a->andor, &b->node);
+ }
+ return a;
+ } else if (b->type == type) {
+ list_push_front(&b->andor, &a->node);
+ return b;
+ } else {
+ struct expr *e = expr_create_andor(type);
+ list_push_back(&e->andor, &a->node);
+ list_push_back(&e->andor, &b->node);
+ return e;
+ }
+}
+
+static void
+expr_insert_andor(struct expr *andor, struct expr *before, struct expr *new)
+{
+ if (new->type == andor->type) {
+ if (andor->type == EXPR_T_AND) {
+ /* Conjunction junction, what's your function? */
+ }
+ list_splice(&before->node, new->andor.next, &new->andor);
+ free(new);
+ } else {
+ list_insert(&before->node, &new->node);
+ }
+}
+
+/* Returns an EXPR_T_BOOLEAN expression with value 'b'. */
+struct expr *
+expr_create_boolean(bool b)
+{
+ struct expr *e = xmalloc(sizeof *e);
+ e->type = EXPR_T_BOOLEAN;
+ e->boolean = b;
+ return e;
+}
+
+static void
+expr_not(struct expr *expr)
+{
+ struct expr *sub;
+
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ expr->cmp.relop = expr_relop_invert(expr->cmp.relop);
+ break;
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ expr_not(sub);
+ }
+ expr->type = expr->type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND;
+ break;
+
+ case EXPR_T_BOOLEAN:
+ expr->boolean = !expr->boolean;
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static struct expr *
+expr_fix_andor(struct expr *expr, bool short_circuit)
+{
+ struct expr *sub, *next;
+
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ if (sub->type == EXPR_T_BOOLEAN) {
+ if (sub->boolean == short_circuit) {
+ expr_destroy(expr);
+ return expr_create_boolean(short_circuit);
+ } else {
+ list_remove(&sub->node);
+ expr_destroy(sub);
+ }
+ }
+ }
+
+ if (list_is_short(&expr->andor)) {
+ if (list_is_empty(&expr->andor)) {
+ free(expr);
+ return expr_create_boolean(!short_circuit);
+ } else {
+ sub = expr_from_node(list_front(&expr->andor));
+ free(expr);
+ return sub;
+ }
+ } else {
+ return expr;
+ }
+}
+
+static struct expr *
+expr_fix(struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return expr;
+
+ case EXPR_T_AND:
+ return expr_fix_andor(expr, false);
+
+ case EXPR_T_OR:
+ return expr_fix_andor(expr, true);
+
+ case EXPR_T_BOOLEAN:
+ return expr;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+/* Formatting. */
+
+static void
+find_bitwise_range(const union mf_subvalue *sv, int width,
+ int *startp, int *n_bitsp)
+{
+ unsigned int start = bitwise_scan(sv, sizeof *sv, true, 0, width);
+ if (start < width) {
+ unsigned int end = bitwise_scan(sv, sizeof *sv, false, start, width);
+ if (end >= width
+ || bitwise_scan(sv, sizeof *sv, true, end, width) >= width) {
+ *startp = start;
+ *n_bitsp = end - start;
+ return;
+ }
+ }
+ *startp = *n_bitsp = 0;
+}
+
+static void
+expr_format_string(const char *s, struct ds *ds)
+{
+ struct json json = {
+ .type = JSON_STRING,
+ .u.string = CONST_CAST(char *, s),
+ };
+ json_to_ds(&json, 0, ds);
+}
+
+static void
+expr_format_cmp(const struct expr *e, struct ds *s)
+{
+ /* The common case is numerical comparisons.
+ * Handle string comparisons as a special case. */
+ if (!e->cmp.symbol->width) {
+ ds_put_format(s, "%s %s ", e->cmp.symbol->name,
+ expr_relop_to_string(e->cmp.relop));
+ expr_format_string(e->cmp.string, s);
+ return;
+ }
+
+ int ofs, n;
+ find_bitwise_range(&e->cmp.mask, e->cmp.symbol->width, &ofs, &n);
+ if (n == 1 && (e->cmp.relop == EXPR_R_EQ || e->cmp.relop == EXPR_R_NE)) {
+ bool positive;
+
+ positive = bitwise_get_bit(&e->cmp.value, sizeof e->cmp.value, ofs);
+ positive ^= e->cmp.relop == EXPR_R_NE;
+ if (!positive) {
+ ds_put_char(s, '!');
+ }
+ ds_put_cstr(s, e->cmp.symbol->name);
+ if (e->cmp.symbol->width > 1) {
+ ds_put_format(s, "[%d]", ofs);
+ }
+ return;
+ }
+
+ ds_put_cstr(s, e->cmp.symbol->name);
+ if (n > 0 && n < e->cmp.symbol->width) {
+ if (n > 1) {
+ ds_put_format(s, "[%d..%d]", ofs, ofs + n - 1);
+ } else {
+ ds_put_format(s, "[%d]", ofs);
+ }
+ }
+
+ ds_put_format(s, " %s ", expr_relop_to_string(e->cmp.relop));
+
+ if (n) {
+ union mf_subvalue value;
+
+ memset(&value, 0, sizeof value);
+ bitwise_copy(&e->cmp.value, sizeof e->cmp.value, ofs,
+ &value, sizeof value, 0,
+ n);
+ mf_format_subvalue(&value, s);
+ } else {
+ mf_format_subvalue(&e->cmp.value, s);
+ ds_put_char(s, '/');
+ mf_format_subvalue(&e->cmp.mask, s);
+ }
+}
+
+static void
+expr_format_andor(const struct expr *e, const char *op, struct ds *s)
+{
+ struct expr *sub;
+ int i = 0;
+
+ LIST_FOR_EACH (sub, node, &e->andor) {
+ if (i++) {
+ ds_put_format(s, " %s ", op);
+ }
+
+ if (sub->type == EXPR_T_AND || sub->type == EXPR_T_OR) {
+ ds_put_char(s, '(');
+ expr_format(sub, s);
+ ds_put_char(s, ')');
+ } else {
+ expr_format(sub, s);
+ }
+ }
+}
+
+/* Appends a string form of 'e' to 's'. The string form is acceptable for
+ * parsing back into an equivalent expression. */
+void
+expr_format(const struct expr *e, struct ds *s)
+{
+ switch (e->type) {
+ case EXPR_T_CMP:
+ expr_format_cmp(e, s);
+ break;
+
+ case EXPR_T_AND:
+ expr_format_andor(e, "&&", s);
+ break;
+
+ case EXPR_T_OR:
+ expr_format_andor(e, "||", s);
+ break;
+
+ case EXPR_T_BOOLEAN:
+ ds_put_char(s, e->boolean ? '1' : '0');
+ break;
+ }
+}
+
+/* Prints a string form of 'e' on stdout, followed by a new-line. */
+void
+expr_print(const struct expr *e)
+{
+ struct ds output;
+
+ ds_init(&output);
+ expr_format(e, &output);
+ puts(ds_cstr(&output));
+ ds_destroy(&output);
+}
+
+/* Parsing. */
+
+/* Type of a "union expr_constant" or "struct expr_constant_set". */
+enum expr_constant_type {
+ EXPR_C_INTEGER,
+ EXPR_C_STRING
+};
+
+/* A string or integer constant (one must know which from context). */
+union expr_constant {
+ /* Integer constant.
+ *
+ * The width of a constant isn't always clear, e.g. if you write "1",
+ * there's no way to tell whether you mean for that to be a 1-bit constant
+ * or a 128-bit constant or somewhere in between. */
+ struct {
+ union mf_subvalue value;
+ union mf_subvalue mask; /* Only initialized if 'masked'. */
+ bool masked;
+
+ enum lex_format format; /* From the constant's lex_token. */
+ };
+
+ /* Null-terminated string constant. */
+ char *string;
+};
+
+/* A collection of "union expr_constant"s of the same type. */
+struct expr_constant_set {
+ union expr_constant *values; /* Constants. */
+ size_t n_values; /* Number of constants. */
+ enum expr_constant_type type; /* Type of the constants. */
+ bool in_curlies; /* Whether the constants were in {}. */
+};
+
+/* A reference to a symbol or a subfield of a symbol.
+ *
+ * For string fields, ofs and n_bits are 0. */
+struct expr_field {
+ const struct expr_symbol *symbol; /* The symbol. */
+ int ofs; /* Starting bit offset. */
+ int n_bits; /* Number of bits. */
+};
+
+/* Context maintained during expr_parse(). */
+struct expr_context {
+ struct lexer *lexer; /* Lexer for pulling more tokens. */
+ const struct shash *symtab; /* Symbol table. */
+ char *error; /* Error, if any, otherwise NULL. */
+ bool not; /* True inside odd number of NOT operators. */
+};
+
+struct expr *expr_parse__(struct expr_context *);
+static void expr_not(struct expr *);
+static void expr_constant_set_destroy(struct expr_constant_set *);
+static bool parse_field(struct expr_context *, struct expr_field *);
+
+static bool
+expr_error_handle_common(struct expr_context *ctx)
+{
+ if (ctx->error) {
+ /* Already have an error, suppress this one since the cascade seems
+ * unlikely to be useful. */
+ return true;
+ } else if (ctx->lexer->token.type == LEX_T_ERROR) {
+ /* The lexer signaled an error. Nothing at the expression level
+ * accepts an error token, so we'll inevitably end up here with some
+ * meaningless parse error. Report the lexical error instead. */
+ ctx->error = xstrdup(ctx->lexer->token.s);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void OVS_PRINTF_FORMAT(2, 3)
+expr_error(struct expr_context *ctx, const char *message, ...)
+{
+ if (expr_error_handle_common(ctx)) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, message);
+ ctx->error = xvasprintf(message, args);
+ va_end(args);
+}
+
+static void OVS_PRINTF_FORMAT(2, 3)
+expr_syntax_error(struct expr_context *ctx, const char *message, ...)
+{
+ if (expr_error_handle_common(ctx)) {
+ return;
+ }
+
+ struct ds s;
+
+ ds_init(&s);
+ ds_put_cstr(&s, "Syntax error ");
+ if (ctx->lexer->token.type == LEX_T_END) {
+ ds_put_cstr(&s, "at end of input ");
+ } else if (ctx->lexer->start) {
+ ds_put_format(&s, "at `%.*s' ",
+ (int) (ctx->lexer->input - ctx->lexer->start),
+ ctx->lexer->start);
+ }
+
+ va_list args;
+ va_start(args, message);
+ ds_put_format_valist(&s, message, args);
+ va_end(args);
+
+ ctx->error = ds_steal_cstr(&s);
+}
+
+static struct expr *
+make_cmp__(const struct expr_field *f, enum expr_relop r,
+ const union expr_constant *c)
+{
+ struct expr *e = xzalloc(sizeof *e);
+ e->type = EXPR_T_CMP;
+ e->cmp.symbol = f->symbol;
+ e->cmp.relop = r;
+ if (f->symbol->width) {
+ bitwise_copy(&c->value, sizeof c->value, 0,
+ &e->cmp.value, sizeof e->cmp.value, f->ofs,
+ f->n_bits);
+ if (c->masked) {
+ bitwise_copy(&c->mask, sizeof c->mask, 0,
+ &e->cmp.mask, sizeof e->cmp.mask, f->ofs,
+ f->n_bits);
+ } else {
+ bitwise_one(&e->cmp.mask, sizeof e->cmp.mask, f->ofs,
+ f->n_bits);
+ }
+ } else {
+ e->cmp.string = xstrdup(c->string);
+ }
+ return e;
+}
+
+/* Returns the minimum reasonable width for integer constant 'c'. */
+static int
+expr_constant_width(const union expr_constant *c)
+{
+ if (c->masked) {
+ return mf_subvalue_width(&c->mask);
+ }
+
+ switch (c->format) {
+ case LEX_F_DECIMAL:
+ case LEX_F_HEXADECIMAL:
+ return mf_subvalue_width(&c->value);
+
+ case LEX_F_IPV4:
+ return 32;
+
+ case LEX_F_IPV6:
+ return 128;
+
+ case LEX_F_ETHERNET:
+ return 48;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static struct expr *
+make_cmp(struct expr_context *ctx,
+ const struct expr_field *f, enum expr_relop r,
+ struct expr_constant_set *cs)
+{
+ struct expr *e = NULL;
+
+ if (cs->type != (f->symbol->width ? EXPR_C_INTEGER : EXPR_C_STRING)) {
+ expr_error(ctx, "Can't compare %s field %s to %s constant.",
+ f->symbol->width ? "integer" : "string",
+ f->symbol->name,
+ cs->type == EXPR_C_INTEGER ? "integer" : "string");
+ goto exit;
+ }
+
+ if (r != EXPR_R_EQ && r != EXPR_R_NE) {
+ if (cs->in_curlies) {
+ expr_error(ctx, "Only == and != operators may be used "
+ "with value sets.");
+ goto exit;
+ }
+ if (f->symbol->level == EXPR_L_NOMINAL ||
+ f->symbol->level == EXPR_L_BOOLEAN) {
+ expr_error(ctx, "Only == and != operators may be used "
+ "with %s field %s.",
+ expr_level_to_string(f->symbol->level),
+ f->symbol->name);
+ goto exit;
+ }
+ if (cs->values[0].masked) {
+ expr_error(ctx, "Only == and != operators may be used with "
+ "masked constants. Consider using subfields instead "
+ "(e.g. eth.src[0..15] > 0x1111 in place of "
+ "eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff).");
+ goto exit;
+ }
+ }
+
+ if (f->symbol->level == EXPR_L_NOMINAL) {
+ if (f->symbol->expansion) {
+ for (size_t i = 0; i < cs->n_values; i++) {
+ const union mf_subvalue *value = &cs->values[i].value;
+ bool positive = (value->integer & htonll(1)) != 0;
+ positive ^= r == EXPR_R_NE;
+ positive ^= ctx->not;
+ if (!positive) {
+ const char *name = f->symbol->name;
+ expr_error(ctx, "Nominal predicate %s may only be tested "
+ "positively, e.g. `%s' or `%s == 1' but not "
+ "`!%s' or `%s == 0'.",
+ name, name, name, name, name);
+ goto exit;
+ }
+ }
+ } else if (r != (ctx->not ? EXPR_R_NE : EXPR_R_EQ)) {
+ expr_error(ctx, "Nominal field %s may only be tested for "
+ "equality (taking enclosing `!' operators into "
+ "account).", f->symbol->name);
+ goto exit;
+ }
+ }
+
+ if (f->symbol->width) {
+ for (size_t i = 0; i < cs->n_values; i++) {
+ int w = expr_constant_width(&cs->values[i]);
+ if (w > f->symbol->width) {
+ expr_error(ctx, "Cannot compare %d-bit constant against "
+ "%d-bit field %s.",
+ w, f->symbol->width, f->symbol->name);
+ goto exit;
+ }
+ }
+ }
+
+ e = make_cmp__(f, r, &cs->values[0]);
+ for (size_t i = 1; i < cs->n_values; i++) {
+ e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
+ e, make_cmp__(f, r, &cs->values[i]));
+ }
+exit:
+ expr_constant_set_destroy(cs);
+ return e;
+}
+
+static bool
+expr_get_int(struct expr_context *ctx, int *value)
+{
+ if (ctx->lexer->token.type == LEX_T_INTEGER
+ && ctx->lexer->token.format == LEX_F_DECIMAL
+ && ntohll(ctx->lexer->token.value.integer) <= INT_MAX) {
+ *value = ntohll(ctx->lexer->token.value.integer);
+ lexer_get(ctx->lexer);
+ return true;
+ } else {
+ expr_syntax_error(ctx, "expecting small integer.");
+ return false;
+ }
+}
+
+static bool
+parse_field(struct expr_context *ctx, struct expr_field *f)
+{
+ const struct expr_symbol *symbol;
+
+ if (ctx->lexer->token.type != LEX_T_ID) {
+ expr_syntax_error(ctx, "expecting field name.");
+ return false;
+ }
+
+ symbol = shash_find_data(ctx->symtab, ctx->lexer->token.s);
+ if (!symbol) {
+ expr_syntax_error(ctx, "expecting field name.");
+ return false;
+ }
+ lexer_get(ctx->lexer);
+
+ f->symbol = symbol;
+ if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) {
+ int low, high;
+
+ if (!symbol->width) {
+ expr_error(ctx, "Cannot select subfield of string field %s.",
+ symbol->name);
+ return false;
+ }
+
+ if (!expr_get_int(ctx, &low)) {
+ return false;
+ }
+ if (lexer_match(ctx->lexer, LEX_T_ELLIPSIS)) {
+ if (!expr_get_int(ctx, &high)) {
+ return false;
+ }
+ } else {
+ high = low;
+ }
+
+ if (!lexer_match(ctx->lexer, LEX_T_RSQUARE)) {
+ expr_syntax_error(ctx, "expecting `]'.");
+ return false;
+ }
+
+ if (low > high) {
+ expr_error(ctx, "Invalid bit range %d to %d.", low, high);
+ return false;
+ } else if (high >= symbol->width) {
+ expr_error(ctx, "Cannot select bits %d to %d of %d-bit field %s.",
+ low, high, symbol->width, symbol->name);
+ return false;
+ } else if (symbol->level == EXPR_L_NOMINAL
+ && (low != 0 || high != symbol->width - 1)) {
+ expr_error(ctx, "Cannot select subfield of nominal field %s.",
+ symbol->name);
+ return false;
+ }
+
+ f->ofs = low;
+ f->n_bits = high - low + 1;
+ } else {
+ f->ofs = 0;
+ f->n_bits = symbol->width;
+ }
+
+ return true;
+}
+
+static bool
+parse_relop(struct expr_context *ctx, enum expr_relop *relop)
+{
+ if (expr_relop_from_token(ctx->lexer->token.type, relop)) {
+ lexer_get(ctx->lexer);
+ return true;
+ } else {
+ expr_syntax_error(ctx, "expecting relational operator.");
+ return false;
+ }
+}
+
+static bool
+assign_constant_set_type(struct expr_context *ctx,
+ struct expr_constant_set *cs,
+ enum expr_constant_type type)
+{
+ if (!cs->n_values || cs->type == type) {
+ cs->type = type;
+ return true;
+ } else {
+ expr_syntax_error(ctx, "expecting %s.",
+ cs->type == EXPR_C_INTEGER ? "integer" : "string");
+ return false;
+ }
+}
+
+static bool
+parse_constant(struct expr_context *ctx, struct expr_constant_set *cs,
+ size_t *allocated_values)
+{
+ if (cs->n_values >= *allocated_values) {
+ cs->values = x2nrealloc(cs->values, allocated_values,
+ sizeof *cs->values);
+ }
+
+ if (ctx->lexer->token.type == LEX_T_STRING) {
+ if (!assign_constant_set_type(ctx, cs, EXPR_C_STRING)) {
+ return false;
+ }
+ cs->values[cs->n_values++].string = xstrdup(ctx->lexer->token.s);
+ lexer_get(ctx->lexer);
+ return true;
+ } else if (ctx->lexer->token.type == LEX_T_INTEGER ||
+ ctx->lexer->token.type == LEX_T_MASKED_INTEGER) {
+ if (!assign_constant_set_type(ctx, cs, EXPR_C_INTEGER)) {
+ return false;
+ }
+
+ union expr_constant *c = &cs->values[cs->n_values++];
+ c->value = ctx->lexer->token.value;
+ c->format = ctx->lexer->token.format;
+ c->masked = ctx->lexer->token.type == LEX_T_MASKED_INTEGER;
+ if (c->masked) {
+ c->mask = ctx->lexer->token.mask;
+ }
+ lexer_get(ctx->lexer);
+ return true;
+ } else {
+ expr_syntax_error(ctx, "expecting constant.");
+ return false;
+ }
+}
+
+/* Parses a single or {}-enclosed set of integer or string constants into 'cs',
+ * which the caller need not have initialized. Returns true on success, in
+ * which case the caller owns 'cs', false on failure, in which case 'cs' is
+ * indeterminate. */
+static bool
+parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs)
+{
+ size_t allocated_values = 0;
+ bool ok;
+
+ memset(cs, 0, sizeof *cs);
+ if (lexer_match(ctx->lexer, LEX_T_LCURLY)) {
+ ok = true;
+ cs->in_curlies = true;
+ do {
+ if (!parse_constant(ctx, cs, &allocated_values)) {
+ ok = false;
+ break;
+ }
+ lexer_match(ctx->lexer, LEX_T_COMMA);
+ } while (!lexer_match(ctx->lexer, LEX_T_RCURLY));
+ } else {
+ ok = parse_constant(ctx, cs, &allocated_values);
+ }
+ if (!ok) {
+ expr_constant_set_destroy(cs);
+ }
+ return ok;
+}
+
+static void
+expr_constant_set_destroy(struct expr_constant_set *cs)
+{
+ if (cs) {
+ if (cs->type == EXPR_C_STRING) {
+ for (size_t i = 0; i < cs->n_values; i++) {
+ free(cs->values[i].string);
+ }
+ }
+ free(cs->values);
+ }
+}
+
+static struct expr *
+expr_parse_primary(struct expr_context *ctx, bool *atomic)
+{
+ *atomic = false;
+ if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+ struct expr *e = expr_parse__(ctx);
+ if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+ expr_destroy(e);
+ expr_syntax_error(ctx, "expecting `)'.");
+ return NULL;
+ }
+ *atomic = true;
+ return e;
+ }
+
+ if (ctx->lexer->token.type == LEX_T_ID) {
+ struct expr_field f;
+ enum expr_relop r;
+ struct expr_constant_set c;
+
+ if (!parse_field(ctx, &f)) {
+ return NULL;
+ }
+
+ if (!expr_relop_from_token(ctx->lexer->token.type, &r)) {
+ if (f.n_bits > 1 && !ctx->not) {
+ expr_error(ctx, "Explicit `!= 0' is required for inequality "
+ "test of multibit field against 0.");
+ return NULL;
+ }
+
+ *atomic = true;
+
+ union expr_constant *cst = xzalloc(sizeof *cst);
+ cst->format = LEX_F_HEXADECIMAL;
+ cst->masked = false;
+
+ c.type = EXPR_C_INTEGER;
+ c.values = cst;
+ c.n_values = 1;
+ c.in_curlies = false;
+ return make_cmp(ctx, &f, EXPR_R_NE, &c);
+ } else if (parse_relop(ctx, &r) && parse_constant_set(ctx, &c)) {
+ return make_cmp(ctx, &f, r, &c);
+ } else {
+ return NULL;
+ }
+ } else {
+ struct expr_constant_set c1;
+ if (!parse_constant_set(ctx, &c1)) {
+ return NULL;
+ }
+
+ if (!expr_relop_from_token(ctx->lexer->token.type, NULL)
+ && c1.n_values == 1
+ && c1.type == EXPR_C_INTEGER
+ && c1.values[0].format == LEX_F_DECIMAL
+ && !c1.values[0].masked
+ && !c1.in_curlies) {
+ uint64_t x = ntohll(c1.values[0].value.integer);
+ if (x <= 1) {
+ *atomic = true;
+ expr_constant_set_destroy(&c1);
+ return expr_create_boolean(x);
+ }
+ }
+
+ enum expr_relop r1;
+ struct expr_field f;
+ if (!parse_relop(ctx, &r1) || !parse_field(ctx, &f)) {
+ expr_constant_set_destroy(&c1);
+ return NULL;
+ }
+
+ if (!expr_relop_from_token(ctx->lexer->token.type, NULL)) {
+ return make_cmp(ctx, &f, expr_relop_turn(r1), &c1);
+ }
+
+ enum expr_relop r2;
+ struct expr_constant_set c2;
+ if (!parse_relop(ctx, &r2) || !parse_constant_set(ctx, &c2)) {
+ expr_constant_set_destroy(&c1);
+ return NULL;
+ } else {
+ /* Reject "1 == field == 2", "1 < field > 2", and so on. */
+ if (!(((r1 == EXPR_R_LT || r1 == EXPR_R_LE) &&
+ (r2 == EXPR_R_LT || r2 == EXPR_R_LE)) ||
+ ((r1 == EXPR_R_GT || r1 == EXPR_R_GE) &&
+ (r2 == EXPR_R_GT || r2 == EXPR_R_GE)))) {
+ expr_error(ctx, "Range expressions must have the form "
+ "`x < field < y' or `x > field > y', with each "
+ "`<' optionally replaced by `<=' or `>' by `>=').");
+ expr_constant_set_destroy(&c1);
+ expr_constant_set_destroy(&c2);
+ return NULL;
+ }
+
+ struct expr *e1 = make_cmp(ctx, &f, expr_relop_turn(r1), &c1);
+ struct expr *e2 = make_cmp(ctx, &f, r2, &c2);
+ if (ctx->error) {
+ expr_destroy(e1);
+ expr_destroy(e2);
+ return NULL;
+ }
+ return expr_combine(EXPR_T_AND, e1, e2);
+ }
+ }
+}
+
+static struct expr *
+expr_parse_not(struct expr_context *ctx)
+{
+ bool atomic;
+
+ if (lexer_match(ctx->lexer, LEX_T_LOG_NOT)) {
+ ctx->not = !ctx->not;
+ struct expr *expr = expr_parse_primary(ctx, &atomic);
+ ctx->not = !ctx->not;
+
+ if (expr) {
+ if (!atomic) {
+ expr_error(ctx, "Missing parentheses around operand of !.");
+ expr_destroy(expr);
+ return NULL;
+ }
+ expr_not(expr);
+ }
+ return expr;
+ } else {
+ return expr_parse_primary(ctx, &atomic);
+ }
+}
+
+struct expr *
+expr_parse__(struct expr_context *ctx)
+{
+ struct expr *e = expr_parse_not(ctx);
+ if (!e) {
+ return NULL;
+ }
+
+ enum lex_type lex_type = ctx->lexer->token.type;
+ if (lex_type == LEX_T_LOG_AND || lex_type == LEX_T_LOG_OR) {
+ enum expr_type expr_type
+ = lex_type == LEX_T_LOG_AND ? EXPR_T_AND : EXPR_T_OR;
+
+ lexer_get(ctx->lexer);
+ do {
+ struct expr *e2 = expr_parse_not(ctx);
+ if (!e2) {
+ expr_destroy(e);
+ return NULL;
+ }
+ e = expr_combine(expr_type, e, e2);
+ } while (lexer_match(ctx->lexer, lex_type));
+ if (ctx->lexer->token.type == LEX_T_LOG_AND
+ || ctx->lexer->token.type == LEX_T_LOG_OR) {
+ expr_destroy(e);
+ expr_error(ctx,
+ "&& and || must be parenthesized when used together.");
+ return NULL;
+ }
+ }
+ return e;
+}
+
+/* Parses an expression using the symbols in 'symtab' from 'lexer'. If
+ * successful, returns the new expression and sets '*errorp' to NULL. On
+ * failure, returns NULL and sets '*errorp' to an explanatory error message.
+ * The caller must eventually free the returned expression (with
+ * expr_destroy()) or error (with free()). */
+struct expr *
+expr_parse(struct lexer *lexer, const struct shash *symtab, char **errorp)
+{
+ struct expr_context ctx;
+
+ ctx.lexer = lexer;
+ ctx.symtab = symtab;
+ ctx.error = NULL;
+ ctx.not = false;
+
+ struct expr *e = expr_parse__(&ctx);
+ *errorp = ctx.error;
+ ovs_assert((ctx.error != NULL) != (e != NULL));
+ return e;
+}
+
+/* Like expr_parse(), but the expression is taken from 's'. */
+struct expr *
+expr_parse_string(const char *s, const struct shash *symtab, char **errorp)
+{
+ struct lexer lexer;
+ struct expr *expr;
+
+ lexer_init(&lexer, s);
+ lexer_get(&lexer);
+ expr = expr_parse(&lexer, symtab, errorp);
+ if (!errorp && lexer.token.type != LEX_T_END) {
+ *errorp = xstrdup("Extra tokens at end of input.");
+ expr_destroy(expr);
+ expr = NULL;
+ }
+ lexer_destroy(&lexer);
+
+ return expr;
+}
+
+static struct expr_symbol *
+add_symbol(struct shash *symtab, const char *name, int width,
+ const char *prereqs, enum expr_level level,
+ bool must_crossproduct)
+{
+ struct expr_symbol *symbol = xzalloc(sizeof *symbol);
+ symbol->name = xstrdup(name);
+ symbol->prereqs = prereqs && prereqs[0] ? xstrdup(prereqs) : NULL;
+ symbol->width = width;
+ symbol->level = level;
+ symbol->must_crossproduct = must_crossproduct;
+ shash_add_assert(symtab, symbol->name, symbol);
+ return symbol;
+}
+
+/* Adds field 'id' to symbol table 'symtab' under the given 'name'. Whenever
+ * 'name' is referenced, expression annotation (see expr_annotate()) will
+ * ensure that 'prereqs' are also true. If 'must_crossproduct' is true, then
+ * conversion to flows will never attempt to use the field as a conjunctive
+ * match dimension (see "Crossproducting" in the large comment on struct
+ * expr_symbol in expr.h for an example).
+ *
+ * A given field 'id' must only be used for a single symbol in a symbol table.
+ * Use subfields to duplicate or subset a field (you can even make a subfield
+ * include all the bits of the "parent" field if you like). */
+struct expr_symbol *
+expr_symtab_add_field(struct shash *symtab, const char *name,
+ enum mf_field_id id, const char *prereqs,
+ bool must_crossproduct)
+{
+ const struct mf_field *field = mf_from_id(id);
+ struct expr_symbol *symbol;
+
+ symbol = add_symbol(symtab, name, field->n_bits, prereqs,
+ (field->maskable == MFM_FULLY
+ ? EXPR_L_ORDINAL
+ : EXPR_L_NOMINAL),
+ must_crossproduct);
+ symbol->field = field;
+ return symbol;
+}
+
+static bool
+parse_field_from_string(const char *s, const struct shash *symtab,
+ struct expr_field *field, char **errorp)
+{
+ struct lexer lexer;
+ lexer_init(&lexer, s);
+ lexer_get(&lexer);
+
+ struct expr_context ctx;
+ ctx.lexer = &lexer;
+ ctx.symtab = symtab;
+ ctx.error = NULL;
+ ctx.not = false;
+
+ bool ok = parse_field(&ctx, field);
+ if (!ok) {
+ *errorp = ctx.error;
+ } else if (lexer.token.type != LEX_T_END) {
+ *errorp = xstrdup("Extra tokens at end of input.");
+ ok = false;
+ }
+
+ lexer_destroy(&lexer);
+
+ return ok;
+}
+
+/* Adds 'name' as a subfield of a larger field in 'symtab'. Whenever
+ * 'name' is referenced, expression annotation (see expr_annotate()) will
+ * ensure that 'prereqs' are also true.
+ *
+ * 'subfield' must describe the subfield as a string, e.g. "vlan.tci[0..11]"
+ * for the low 12 bits of a larger field named "vlan.tci". */
+struct expr_symbol *
+expr_symtab_add_subfield(struct shash *symtab, const char *name,
+ const char *prereqs, const char *subfield)
+{
+ struct expr_symbol *symbol;
+ struct expr_field f;
+ char *error;
+
+ if (!parse_field_from_string(subfield, symtab, &f, &error)) {
+ VLOG_WARN("%s: error parsing %s subfield (%s)", subfield, name, error);
+ free(error);
+ return NULL;
+ }
+
+ enum expr_level level = f.symbol->level;
+ if (level != EXPR_L_ORDINAL) {
+ VLOG_WARN("can't define %s as subfield of %s field %s",
+ name, expr_level_to_string(level), f.symbol->name);
+ }
+
+ symbol = add_symbol(symtab, name, f.n_bits, prereqs, level, false);
+ symbol->expansion = xstrdup(subfield);
+ return symbol;
+}
+
+/* Adds a string-valued symbol named 'name' to 'symtab' with the specified
+ * 'prereqs'. */
+struct expr_symbol *
+expr_symtab_add_string(struct shash *symtab, const char *name,
+ const char *prereqs)
+{
+ return add_symbol(symtab, name, 0, prereqs, EXPR_L_NOMINAL, false);
+}
+
+static enum expr_level
+expr_get_level(const struct expr *expr)
+{
+ const struct expr *sub;
+ enum expr_level level = EXPR_L_ORDINAL;
+
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return (expr->cmp.symbol->level == EXPR_L_NOMINAL
+ ? EXPR_L_NOMINAL
+ : EXPR_L_BOOLEAN);
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ enum expr_level sub_level = expr_get_level(sub);
+ level = MIN(level, sub_level);
+ }
+ return level;
+
+ case EXPR_T_BOOLEAN:
+ return EXPR_L_BOOLEAN;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static enum expr_level
+expr_parse_level(const char *s, const struct shash *symtab, char **errorp)
+{
+ struct expr *expr = expr_parse_string(s, symtab, errorp);
+ enum expr_level level = expr ? expr_get_level(expr) : EXPR_L_NOMINAL;
+ expr_destroy(expr);
+ return level;
+}
+
+/* Adds a predicate symbol, whose value is the given Boolean 'expression',
+ * named 'name' to 'symtab'. For example, "ip4 && ip4.proto == 1" might be an
+ * appropriate predicate named "tcp4". */
+struct expr_symbol *
+expr_symtab_add_predicate(struct shash *symtab, const char *name,
+ const char *expansion)
+{
+ struct expr_symbol *symbol;
+ enum expr_level level;
+ char *error;
+
+ level = expr_parse_level(expansion, symtab, &error);
+ if (error) {
+ VLOG_WARN("%s: error parsing %s expansion (%s)",
+ expansion, name, error);
+ free(error);
+ return NULL;
+ }
+
+ symbol = add_symbol(symtab, name, 1, NULL, level, false);
+ symbol->expansion = xstrdup(expansion);
+ return symbol;
+}
+
+/* Destroys 'symtab' and all of its symbols. */
+void
+expr_symtab_destroy(struct shash *symtab)
+{
+ struct shash_node *node, *next;
+
+ SHASH_FOR_EACH_SAFE (node, next, symtab) {
+ struct expr_symbol *symbol = node->data;
+
+ shash_delete(symtab, node);
+ free(symbol->name);
+ free(symbol->prereqs);
+ free(symbol->expansion);
+ free(symbol);
+ }
+}
+
+/* Cloning. */
+
+static struct expr *
+expr_clone_cmp(struct expr *expr)
+{
+ struct expr *new = xmemdup(expr, sizeof *expr);
+ if (!new->cmp.symbol->width) {
+ new->cmp.string = xstrdup(new->cmp.string);
+ }
+ return new;
+}
+
+static struct expr *
+expr_clone_andor(struct expr *expr)
+{
+ struct expr *new = expr_create_andor(expr->type);
+ struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ struct expr *new_sub = expr_clone(sub);
+ list_push_back(&new->andor, &new_sub->node);
+ }
+ return new;
+}
+
+/* Returns a clone of 'expr'. This is a "deep copy": neither the returned
+ * expression nor any of its substructure will be shared with 'expr'. */
+struct expr *
+expr_clone(struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return expr_clone_cmp(expr);
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ return expr_clone_andor(expr);
+
+ case EXPR_T_BOOLEAN:
+ return expr_create_boolean(expr->boolean);
+ }
+ OVS_NOT_REACHED();
+}
+
+/* Destroys 'expr' and all of the sub-expressions it references. */
+void
+expr_destroy(struct expr *expr)
+{
+ if (!expr) {
+ return;
+ }
+
+ struct expr *sub, *next;
+
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ if (!expr->cmp.symbol->width) {
+ free(expr->cmp.string);
+ }
+ break;
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ list_remove(&sub->node);
+ expr_destroy(sub);
+ }
+ break;
+
+ case EXPR_T_BOOLEAN:
+ break;
+ }
+ free(expr);
+}
+
+/* Annotation. */
+
+/* An element in a linked list of symbols.
+ *
+ * Used to detect when a symbol is being expanded recursively, to allow
+ * flagging an error. */
+struct annotation_nesting {
+ struct ovs_list node;
+ const struct expr_symbol *symbol;
+};
+
+struct expr *expr_annotate__(struct expr *, const struct shash *symtab,
+ struct ovs_list *nesting, char **errorp);
+
+static struct expr *
+parse_and_annotate(const char *s, const struct shash *symtab,
+ struct ovs_list *nesting, char **errorp)
+{
+ char *error;
+ struct expr *expr;
+
+ expr = expr_parse_string(s, symtab, &error);
+ if (expr) {
+ expr = expr_annotate__(expr, symtab, nesting, &error);
+ }
+ if (!expr) {
+ *errorp = xasprintf("Error parsing expression `%s' encountered as "
+ "prerequisite or predicate of initial expression: "
+ "%s", s, error);
+ free(error);
+ }
+ return expr;
+}
+
+static struct expr *
+expr_annotate_cmp(struct expr *expr, const struct shash *symtab,
+ struct ovs_list *nesting, char **errorp)
+{
+ const struct expr_symbol *symbol = expr->cmp.symbol;
+ const struct annotation_nesting *iter;
+ LIST_FOR_EACH (iter, node, nesting) {
+ if (iter->symbol == symbol) {
+ *errorp = xasprintf("Recursive expansion of symbol `%s'.",
+ symbol->name);
+ expr_destroy(expr);
+ return NULL;
+ }
+ }
+
+ struct annotation_nesting an;
+ an.symbol = symbol;
+ list_push_back(nesting, &an.node);
+
+ struct expr *prereqs = NULL;
+ if (symbol->prereqs) {
+ prereqs = parse_and_annotate(symbol->prereqs, symtab, nesting, errorp);
+ if (!prereqs) {
+ goto error;
+ }
+ }
+
+ if (symbol->expansion) {
+ if (symbol->level == EXPR_L_ORDINAL) {
+ struct expr_field field;
+
+ if (!parse_field_from_string(symbol->expansion, symtab,
+ &field, errorp)) {
+ goto error;
+ }
+
+ expr->cmp.symbol = field.symbol;
+ mf_subvalue_shift(&expr->cmp.value, field.ofs);
+ mf_subvalue_shift(&expr->cmp.mask, field.ofs);
+ } else {
+ struct expr *expansion;
+
+ expansion = parse_and_annotate(symbol->expansion, symtab,
+ nesting, errorp);
+ if (!expansion) {
+ goto error;
+ }
+
+ bool positive = (expr->cmp.value.integer & htonll(1)) != 0;
+ positive ^= expr->cmp.relop == EXPR_R_NE;
+ if (!positive) {
+ expr_not(expansion);
+ }
+
+ expr_destroy(expr);
+ expr = expansion;
+ }
+ }
+
+ list_remove(&an.node);
+ return prereqs ? expr_combine(EXPR_T_AND, expr, prereqs) : expr;
+
+error:
+ expr_destroy(expr);
+ expr_destroy(prereqs);
+ list_remove(&an.node);
+ return NULL;
+}
+
+struct expr *
+expr_annotate__(struct expr *expr, const struct shash *symtab,
+ struct ovs_list *nesting, char **errorp)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return expr_annotate_cmp(expr, symtab, nesting, errorp);
+
+ case EXPR_T_AND:
+ case EXPR_T_OR: {
+ struct expr *sub, *next;
+
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ list_remove(&sub->node);
+ struct expr *new_sub = expr_annotate__(sub, symtab,
+ nesting, errorp);
+ if (!new_sub) {
+ expr_destroy(expr);
+ return NULL;
+ }
+ expr_insert_andor(expr, next, new_sub);
+ }
+ *errorp = NULL;
+ return expr;
+ }
+
+ case EXPR_T_BOOLEAN:
+ *errorp = NULL;
+ return expr;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+/* "Annotates" 'expr', which does the following:
+ *
+ * - Applies prerequisites, by locating each comparison operator whose
+ * field has a prerequisite and adding a logical AND against those
+ * prerequisites.
+ *
+ * - Expands references to subfield symbols, by replacing them by
+ * references to their underlying field symbols (suitably shifted).
+ *
+ * - Expands references to predicate symbols, by replacing them by the
+ * expressions that they expand to.
+ *
+ * In each case, annotation occurs recursively as necessary. */
+struct expr *
+expr_annotate(struct expr *expr, const struct shash *symtab, char **errorp)
+{
+ struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting);
+ return expr_annotate__(expr, symtab, &nesting, errorp);
+}
+
+static struct expr *
+expr_simplify_ne(struct expr *expr)
+{
+ struct expr *new = NULL;
+ const union mf_subvalue *value = &expr->cmp.value;
+ const union mf_subvalue *mask = &expr->cmp.mask;
+ int w = expr->cmp.symbol->width;
+ int i;
+
+ for (i = 0; (i = bitwise_scan(mask, sizeof *mask, true, i, w)) < w; i++) {
+ struct expr *e;
+
+ e = xzalloc(sizeof *e);
+ e->type = EXPR_T_CMP;
+ e->cmp.symbol = expr->cmp.symbol;
+ e->cmp.relop = EXPR_R_EQ;
+ bitwise_put_bit(&e->cmp.value, sizeof e->cmp.value, i,
+ !bitwise_get_bit(value, sizeof *value, i));
+ bitwise_put1(&e->cmp.mask, sizeof e->cmp.mask, i);
+
+ new = expr_combine(EXPR_T_OR, new, e);
+ }
+ ovs_assert(new);
+
+ expr_destroy(expr);
+
+ return new;
+}
+
+static struct expr *
+expr_simplify_relational(struct expr *expr)
+{
+ const union mf_subvalue *value = &expr->cmp.value;
+ int start, n_bits, end;
+
+ find_bitwise_range(&expr->cmp.mask, expr->cmp.symbol->width,
+ &start, &n_bits);
+ ovs_assert(n_bits > 0);
+ end = start + n_bits;
+
+ struct expr *new;
+ if (expr->cmp.relop == EXPR_R_LE || expr->cmp.relop == EXPR_R_GE) {
+ new = xmemdup(expr, sizeof *expr);
+ new->cmp.relop = EXPR_R_EQ;
+ } else {
+ new = NULL;
+ }
+
+ bool b = expr->cmp.relop == EXPR_R_LT || expr->cmp.relop == EXPR_R_LE;
+ for (int z = bitwise_scan(value, sizeof *value, b, start, end);
+ z < end;
+ z = bitwise_scan(value, sizeof *value, b, z + 1, end)) {
+ struct expr *e;
+
+ e = xmemdup(expr, sizeof *expr);
+ e->cmp.relop = EXPR_R_EQ;
+ bitwise_toggle_bit(&e->cmp.value, sizeof e->cmp.value, z);
+ bitwise_zero(&e->cmp.value, sizeof e->cmp.value, start, z - start);
+ bitwise_zero(&e->cmp.mask, sizeof e->cmp.mask, start, z - start);
+ new = expr_combine(EXPR_T_OR, new, e);
+ }
+ expr_destroy(expr);
+ return new ? new : expr_create_boolean(false);
+}
+
+/* Takes ownership of 'expr' and returns an equivalent expression whose
+ * EXPR_T_CMP nodes use only tests for equality (EXPR_R_EQ). */
+struct expr *
+expr_simplify(struct expr *expr)
+{
+ struct expr *sub, *next;
+
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return (expr->cmp.relop == EXPR_R_EQ || !expr->cmp.symbol->width ? expr
+ : expr->cmp.relop == EXPR_R_NE ? expr_simplify_ne(expr)
+ : expr_simplify_relational(expr));
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ list_remove(&sub->node);
+ expr_insert_andor(expr, next, expr_simplify(sub));
+ }
+ return expr_fix(expr);
+
+ case EXPR_T_BOOLEAN:
+ return expr;
+ }
+ OVS_NOT_REACHED();
+}
+
+static const struct expr_symbol *
+expr_is_cmp(const struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return expr->cmp.symbol;
+
+ case EXPR_T_AND:
+ case EXPR_T_OR: {
+ const struct expr_symbol *prev = NULL;
+ struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ const struct expr_symbol *symbol = expr_is_cmp(sub);
+ if (!symbol || (prev && symbol != prev)) {
+ return NULL;
+ }
+ prev = symbol;
+ }
+ return prev;
+ }
+
+ case EXPR_T_BOOLEAN:
+ return NULL;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+struct expr_sort {
+ struct expr *expr;
+ const struct expr_symbol *relop;
+ enum expr_type type;
+};
+
+static int
+compare_expr_sort(const void *a_, const void *b_)
+{
+ const struct expr_sort *a = a_;
+ const struct expr_sort *b = b_;
+
+ if (a->type != b->type) {
+ return a->type < b->type ? -1 : 1;
+ } else if (a->relop) {
+ int cmp = strcmp(a->relop->name, b->relop->name);
+ if (cmp) {
+ return cmp;
+ }
+
+ enum expr_type a_type = a->expr->type;
+ enum expr_type b_type = a->expr->type;
+ return a_type < b_type ? -1 : a_type > b_type;
+ } else if (a->type == EXPR_T_AND || a->type == EXPR_T_OR) {
+ size_t a_len = list_size(&a->expr->andor);
+ size_t b_len = list_size(&b->expr->andor);
+ return a_len < b_len ? -1 : a_len > b_len;
+ } else {
+ return 0;
+ }
+}
+
+static struct expr *crush_cmps(struct expr *, const struct expr_symbol *);
+
+static struct expr *
+crush_and(struct expr *expr, const struct expr_symbol *symbol)
+{
+ ovs_assert(!list_is_short(&expr->andor));
+
+ union mf_subvalue value, mask;
+ memset(&value, 0, sizeof value);
+ memset(&mask, 0, sizeof mask);
+
+ struct expr *sub, *next = NULL;
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ list_remove(&sub->node);
+ struct expr *new = crush_cmps(sub, symbol);
+ switch (new->type) {
+ case EXPR_T_CMP:
+ if (!mf_subvalue_intersect(&value, &mask,
+ &new->cmp.value, &new->cmp.mask,
+ &value, &mask)) {
+ expr_destroy(new);
+ expr_destroy(expr);
+ return expr_create_boolean(false);
+ }
+ expr_destroy(new);
+ break;
+ case EXPR_T_AND:
+ OVS_NOT_REACHED();
+ case EXPR_T_OR:
+ list_insert(&next->node, &new->node);
+ break;
+ case EXPR_T_BOOLEAN:
+ if (!new->boolean) {
+ expr_destroy(expr);
+ return new;
+ }
+ free(new);
+ break;
+ }
+ }
+ if (list_is_empty(&expr->andor)) {
+ if (is_all_zeros(&mask, sizeof mask)) {
+ expr_destroy(expr);
+ return expr_create_boolean(true);
+ } else {
+ struct expr *cmp;
+ cmp = xmalloc(sizeof *cmp);
+ cmp->type = EXPR_T_CMP;
+ cmp->cmp.symbol = symbol;
+ cmp->cmp.relop = EXPR_R_EQ;
+ cmp->cmp.value = value;
+ cmp->cmp.mask = mask;
+ expr_destroy(expr);
+ return cmp;
+ }
+ } else if (list_is_short(&expr->andor)) {
+ /* Transform "a && (b || c || d)" into "ab || ac || ad" where "ab" is
+ * computed as "a && b", etc. */
+ struct expr *disjuncts = expr_from_node(list_pop_front(&expr->andor));
+ struct expr *or;
+
+ or = xmalloc(sizeof *or);
+ or->type = EXPR_T_OR;
+ list_init(&or->andor);
+
+ ovs_assert(disjuncts->type == EXPR_T_OR);
+ LIST_FOR_EACH_SAFE (sub, next, node, &disjuncts->andor) {
+ ovs_assert(sub->type == EXPR_T_CMP);
+ list_remove(&sub->node);
+ if (mf_subvalue_intersect(&value, &mask,
+ &sub->cmp.value, &sub->cmp.mask,
+ &sub->cmp.value, &sub->cmp.mask)) {
+ list_push_back(&or->andor, &sub->node);
+ } else {
+ free(sub);
+ }
+ }
+ free(disjuncts);
+ free(expr);
+ if (list_is_empty(&or->andor)) {
+ free(or);
+ return expr_create_boolean(false);
+ } else if (list_is_short(&or->andor)) {
+ struct expr *cmp = expr_from_node(list_pop_front(&or->andor));
+ free(or);
+ return cmp;
+ } else {
+ return or;
+ }
+ } else {
+ /* Transform "x && (a0 || a1) && (b0 || b1) && ..." into
+ * "(xa0b0 || xa0b1 || xa1b0 || xa1b1) && ...". */
+ struct expr *as = expr_from_node(list_pop_front(&expr->andor));
+ struct expr *bs = expr_from_node(list_pop_front(&expr->andor));
+ struct expr *new = NULL;
+ struct expr *or;
+
+ or = xmalloc(sizeof *or);
+ or->type = EXPR_T_OR;
+ list_init(&or->andor);
+
+ struct expr *a;
+ LIST_FOR_EACH (a, node, &as->andor) {
+ union mf_subvalue a_value, a_mask;
+
+ ovs_assert(a->type == EXPR_T_CMP);
+ if (!mf_subvalue_intersect(&value, &mask,
+ &a->cmp.value, &a->cmp.mask,
+ &a_value, &a_mask)) {
+ continue;
+ }
+
+ struct expr *b;
+ LIST_FOR_EACH (b, node, &bs->andor) {
+ ovs_assert(b->type == EXPR_T_CMP);
+ if (!new) {
+ new = xmalloc(sizeof *new);
+ new->type = EXPR_T_CMP;
+ new->cmp.symbol = symbol;
+ new->cmp.relop = EXPR_R_EQ;
+ }
+ if (mf_subvalue_intersect(&a_value, &a_mask,
+ &b->cmp.value, &b->cmp.mask,
+ &new->cmp.value, &new->cmp.mask)) {
+ list_push_back(&or->andor, &new->node);
+ new = NULL;
+ }
+ }
+ }
+ expr_destroy(as);
+ expr_destroy(bs);
+ free(new);
+
+ if (list_is_empty(&or->andor)) {
+ expr_destroy(expr);
+ free(or);
+ return expr_create_boolean(false);
+ } else if (list_is_short(&or->andor)) {
+ struct expr *cmp = expr_from_node(list_pop_front(&or->andor));
+ free(or);
+ if (list_is_empty(&expr->andor)) {
+ expr_destroy(expr);
+ return crush_cmps(cmp, symbol);
+ } else {
+ return crush_cmps(expr_combine(EXPR_T_AND, cmp, expr), symbol);
+ }
+ } else if (!list_is_empty(&expr->andor)) {
+ struct expr *e = expr_combine(EXPR_T_AND, or, expr);
+ ovs_assert(!list_is_short(&e->andor));
+ return crush_cmps(e, symbol);
+ } else {
+ expr_destroy(expr);
+ return crush_cmps(or, symbol);
+ }
+ }
+}
+
+static int
+compare_expr(const void *a_, const void *b_)
+{
+ const struct expr *const *ap = a_;
+ const struct expr *const *bp = b_;
+ const struct expr *a = *ap;
+ const struct expr *b = *bp;
+ int d = memcmp(&a->cmp.value, &b->cmp.value, sizeof a->cmp.value);
+ if (!d) {
+ d = memcmp(&a->cmp.mask, &b->cmp.mask, sizeof a->cmp.mask);
+ }
+ return d;
+}
+
+static struct expr *
+crush_or(struct expr *expr, const struct expr_symbol *symbol)
+{
+ struct expr *sub, *next = NULL;
+
+ /* First, crush all the subexpressions. That might eliminate the
+ * OR-expression entirely; if so, return the result. */
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ list_remove(&sub->node);
+ expr_insert_andor(expr, next, crush_cmps(sub, symbol));
+ }
+ expr = expr_fix(expr);
+ if (expr->type != EXPR_T_OR) {
+ return expr;
+ }
+
+ /* Eliminate duplicates by sorting the subexpressions. */
+ size_t n = list_size(&expr->andor);
+ struct expr **subs = xmalloc(n * sizeof *subs);
+
+ size_t i = 0;
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ subs[i++] = sub;
+ }
+ ovs_assert(i == n);
+
+ qsort(subs, n, sizeof *subs, compare_expr);
+
+ list_init(&expr->andor);
+ list_push_back(&expr->andor, &subs[0]->node);
+ for (i = 1; i < n; i++) {
+ struct expr *a = expr_from_node(list_back(&expr->andor));
+ struct expr *b = subs[i];
+ if (memcmp(&a->cmp.value, &b->cmp.value, sizeof a->cmp.value)
+ || memcmp(&a->cmp.mask, &b->cmp.mask, sizeof a->cmp.mask)) {
+ list_push_back(&expr->andor, &b->node);
+ } else {
+ free(b);
+ }
+ }
+ free(subs);
+ return expr_fix(expr);
+}
+
+/* Converts 'expr', which must be a cmp in the sense determined by
+ * expr_is_cmp(). Returns a cmp, a disjunction of cmps, or a boolean. */
+static struct expr *
+crush_cmps(struct expr *expr, const struct expr_symbol *symbol)
+{
+ switch (expr->type) {
+ case EXPR_T_OR:
+ return crush_or(expr, symbol);
+
+ case EXPR_T_AND:
+ return crush_and(expr, symbol);
+
+ case EXPR_T_CMP:
+ return expr;
+
+ case EXPR_T_BOOLEAN:
+ return expr;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static struct expr *
+expr_sort(struct expr *expr)
+{
+ size_t n = list_size(&expr->andor);
+ struct expr_sort *subs = xmalloc(n * sizeof *subs);
+ struct expr *sub;
+ size_t i;
+
+ i = 0;
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ subs[i].expr = sub;
+ subs[i].relop = expr_is_cmp(sub);
+ subs[i].type = subs[i].relop ? EXPR_T_CMP : sub->type;
+ i++;
+ }
+ ovs_assert(i == n);
+
+ qsort(subs, n, sizeof *subs, compare_expr_sort);
+
+ list_init(&expr->andor);
+ for (int i = 0; i < n; ) {
+ if (subs[i].relop) {
+ int j;
+ for (j = i + 1; j < n; j++) {
+ if (subs[i].relop != subs[j].relop) {
+ break;
+ }
+ }
+
+ struct expr *crushed;
+ if (j == i + 1) {
+ crushed = crush_cmps(subs[i].expr, subs[i].relop);
+ } else {
+ struct expr *combined = subs[i].expr;
+ for (int k = i + 1; k < j; k++) {
+ combined = expr_combine(EXPR_T_AND, combined,
+ subs[k].expr);
+ }
+ ovs_assert(!list_is_short(&combined->andor));
+ crushed = crush_cmps(combined, subs[i].relop);
+ }
+ if (crushed->type == EXPR_T_BOOLEAN) {
+ if (!crushed->boolean) {
+ for (int k = j; k < n; k++) {
+ expr_destroy(subs[k].expr);
+ }
+ expr_destroy(expr);
+ expr = crushed;
+ break;
+ } else {
+ free(crushed);
+ }
+ } else {
+ expr = expr_combine(EXPR_T_AND, expr, crushed);
+ }
+ i = j;
+ } else {
+ expr = expr_combine(EXPR_T_AND, expr, subs[i++].expr);
+ }
+ }
+ free(subs);
+
+ return expr;
+}
+
+static struct expr *expr_normalize_or(struct expr *expr);
+
+/* Returns 'expr', which is an AND, reduced to OR(AND(clause)) where
+ * a clause is a cmp or a disjunction of cmps on a single field. */
+static struct expr *
+expr_normalize_and(struct expr *expr)
+{
+ ovs_assert(expr->type == EXPR_T_AND);
+
+ expr = expr_sort(expr);
+ if (expr->type != EXPR_T_AND) {
+ ovs_assert(expr->type == EXPR_T_BOOLEAN);
+ return expr;
+ }
+
+ struct expr *a, *b;
+ LIST_FOR_EACH_SAFE (a, b, node, &expr->andor) {
+ if (&b->node == &expr->andor
+ || a->type != EXPR_T_CMP || b->type != EXPR_T_CMP) {
+ } else if (a->cmp.symbol != b->cmp.symbol) {
+ continue;
+ } else if (mf_subvalue_intersect(&a->cmp.value, &a->cmp.mask,
+ &b->cmp.value, &b->cmp.mask,
+ &b->cmp.value, &b->cmp.mask)) {
+ list_remove(&a->node);
+ expr_destroy(a);
+ } else {
+ expr_destroy(expr);
+ return expr_create_boolean(false);
+ }
+ }
+ if (list_is_short(&expr->andor)) {
+ struct expr *sub = expr_from_node(list_front(&expr->andor));
+ free(expr);
+ return sub;
+ }
+
+ struct expr *sub;
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (sub->type == EXPR_T_CMP) {
+ continue;
+ }
+
+ ovs_assert(sub->type == EXPR_T_OR);
+ const struct expr_symbol *symbol = expr_is_cmp(sub);
+ if (!symbol || symbol->must_crossproduct) {
+ struct expr *or = expr_create_andor(EXPR_T_OR);
+ struct expr *k;
+
+ LIST_FOR_EACH (k, node, &sub->andor) {
+ struct expr *and = expr_create_andor(EXPR_T_AND);
+ struct expr *m;
+
+ LIST_FOR_EACH (m, node, &expr->andor) {
+ struct expr *term = m == sub ? k : m;
+ if (term->type == EXPR_T_AND) {
+ struct expr *p;
+
+ LIST_FOR_EACH (p, node, &term->andor) {
+ struct expr *new = expr_clone(p);
+ list_push_back(&and->andor, &new->node);
+ }
+ } else {
+ struct expr *new = expr_clone(term);
+ list_push_back(&and->andor, &new->node);
+ }
+ }
+ list_push_back(&or->andor, &and->node);
+ }
+ expr_destroy(expr);
+ return expr_normalize_or(or);
+ }
+ }
+ return expr;
+}
+
+static struct expr *
+expr_normalize_or(struct expr *expr)
+{
+ struct expr *sub, *next;
+
+ LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
+ if (sub->type == EXPR_T_AND) {
+ list_remove(&sub->node);
+
+ struct expr *new = expr_normalize_and(sub);
+ if (new->type == EXPR_T_BOOLEAN) {
+ if (new->boolean) {
+ expr_destroy(expr);
+ return new;
+ }
+ free(new);
+ } else {
+ expr_insert_andor(expr, next, new);
+ }
+ } else {
+ ovs_assert(sub->type == EXPR_T_CMP);
+ }
+ }
+ if (list_is_empty(&expr->andor)) {
+ free(expr);
+ return expr_create_boolean(false);
+ }
+ if (list_is_short(&expr->andor)) {
+ struct expr *sub = expr_from_node(list_pop_front(&expr->andor));
+ free(expr);
+ return sub;
+ }
+
+ return expr;
+}
+
+/* Takes ownership of 'expr', which is either a constant "true" or "false" or
+ * an expression in terms of only relationals, AND, and OR. Returns either a
+ * constant "true" or "false" or 'expr' reduced to OR(AND(clause)) where a
+ * clause is a cmp or a disjunction of cmps on a single field. This form is
+ * significant because it is a form that can be directly converted to OpenFlow
+ * flows with the Open vSwitch "conjunctive match" extension.
+ *
+ * 'expr' must already have been simplified, with expr_simplify(). */
+struct expr *
+expr_normalize(struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return expr;
+
+ case EXPR_T_AND:
+ return expr_normalize_and(expr);
+
+ case EXPR_T_OR:
+ return expr_normalize_or(expr);
+
+ case EXPR_T_BOOLEAN:
+ return expr;
+ }
+ OVS_NOT_REACHED();
+}
+
+/* Creates, initializes, and returns a new 'struct expr_match'. If 'm' is
+ * nonnull then it is copied into the new expr_match, otherwise the new
+ * expr_match's 'match' member is initialized to a catch-all match for the
+ * caller to refine in-place.
+ *
+ * If 'conj_id' is nonzero, adds one conjunction based on 'conj_id', 'clause',
+ * and 'n_clauses' to the returned 'struct expr_match', otherwise the
+ * expr_match will not have any conjunctions.
+ *
+ * The caller should use expr_match_add() to add the expr_match to a hash table
+ * after it is finalized. */
+static struct expr_match *
+expr_match_new(const struct match *m, uint8_t clause, uint8_t n_clauses,
+ uint32_t conj_id)
+{
+ struct expr_match *match = xmalloc(sizeof *match);
+ if (m) {
+ match->match = *m;
+ } else {
+ match_init_catchall(&match->match);
+ }
+ if (conj_id) {
+ match->conjunctions = xmalloc(sizeof *match->conjunctions);
+ match->conjunctions[0].id = conj_id;
+ match->conjunctions[0].clause = clause;
+ match->conjunctions[0].n_clauses = n_clauses;
+ match->n = 1;
+ match->allocated = 1;
+ } else {
+ match->conjunctions = NULL;
+ match->n = 0;
+ match->allocated = 0;
+ }
+ return match;
+}
+
+/* Adds 'match' to hash table 'matches', which becomes the new owner of
+ * 'match'.
+ *
+ * This might actually destroy 'match' because it gets merged together with
+ * some existing conjunction.*/
+static void
+expr_match_add(struct hmap *matches, struct expr_match *match)
+{
+ uint32_t hash = match_hash(&match->match, 0);
+ struct expr_match *m;
+
+ HMAP_FOR_EACH_WITH_HASH (m, hmap_node, hash, matches) {
+ if (match_equal(&m->match, &match->match)) {
+ if (!m->n || !match->n) {
+ free(m->conjunctions);
+ m->conjunctions = NULL;
+ m->n = 0;
+ m->allocated = 0;
+ } else {
+ ovs_assert(match->n == 1);
+ if (m->n >= m->allocated) {
+ m->conjunctions = x2nrealloc(m->conjunctions,
+ &m->allocated,
+ sizeof *m->conjunctions);
+ }
+ m->conjunctions[m->n++] = match->conjunctions[0];
+ }
+ free(match->conjunctions);
+ free(match);
+ return;
+ }
+ }
+
+ hmap_insert(matches, &match->hmap_node, hash);
+}
+
+static void
+constrain_match(const struct expr *expr, struct match *m)
+{
+ ovs_assert(expr->type == EXPR_T_CMP);
+ ovs_assert(expr->cmp.symbol->width);
+ mf_mask_subfield(expr->cmp.symbol->field, &expr->cmp.value,
+ &expr->cmp.mask, m);
+}
+
+static void
+add_disjunction(const struct expr *or, struct match *m, uint8_t clause,
+ uint8_t n_clauses, uint32_t conj_id, struct hmap *matches)
+{
+ struct expr *sub;
+
+ ovs_assert(or->type == EXPR_T_OR);
+ LIST_FOR_EACH (sub, node, &or->andor) {
+ struct expr_match *match = expr_match_new(m, clause, n_clauses,
+ conj_id);
+ constrain_match(sub, &match->match);
+ expr_match_add(matches, match);
+ }
+}
+
+static void
+add_conjunction(const struct expr *and, uint32_t *n_conjsp,
+ struct hmap *matches)
+{
+ struct match match;
+ int n_clauses = 0;
+ struct expr *sub;
+
+ match_init_catchall(&match);
+
+ ovs_assert(and->type == EXPR_T_AND);
+ LIST_FOR_EACH (sub, node, &and->andor) {
+ switch (sub->type) {
+ case EXPR_T_CMP:
+ constrain_match(sub, &match);
+ break;
+ case EXPR_T_OR:
+ n_clauses++;
+ break;
+ case EXPR_T_AND:
+ case EXPR_T_BOOLEAN:
+ OVS_NOT_REACHED();
+ }
+ }
+
+ if (!n_clauses) {
+ expr_match_add(matches, expr_match_new(&match, 0, 0, 0));
+ } else if (n_clauses == 1) {
+ LIST_FOR_EACH (sub, node, &and->andor) {
+ if (sub->type == EXPR_T_OR) {
+ add_disjunction(sub, &match, 0, 0, 0, matches);
+ }
+ }
+ } else {
+ int clause = 0;
+ (*n_conjsp)++;
+ LIST_FOR_EACH (sub, node, &and->andor) {
+ if (sub->type == EXPR_T_OR) {
+ add_disjunction(sub, &match, clause++, n_clauses, *n_conjsp,
+ matches);
+ }
+ }
+ }
+}
+
+static void
+add_cmp_flow(const struct expr *cmp, struct hmap *matches)
+{
+ struct expr_match *m = expr_match_new(NULL, 0, 0, 0);
+ constrain_match(cmp, &m->match);
+ expr_match_add(matches, m);
+}
+
+/* Converts 'expr', which must be in the form returned by expr_normalize(), to
+ * a collection of Open vSwitch flows in 'matches', which this function
+ * initializes to an hmap of "struct expr_match" structures. */
+uint32_t
+expr_to_matches(const struct expr *expr, struct hmap *matches)
+{
+ uint32_t n_conjs = 0;
+
+ hmap_init(matches);
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ add_cmp_flow(expr, matches);
+ break;
+
+ case EXPR_T_AND:
+ add_conjunction(expr, &n_conjs, matches);
+ break;
+
+ case EXPR_T_OR:
+ if (expr_is_cmp(expr)) {
+ struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ add_cmp_flow(sub, matches);
+ }
+ } else {
+ struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (sub->type == EXPR_T_AND) {
+ add_conjunction(sub, &n_conjs, matches);
+ } else {
+ add_cmp_flow(sub, matches);
+ }
+ }
+ }
+ break;
+
+ case EXPR_T_BOOLEAN:
+ if (expr->boolean) {
+ struct expr_match *m = expr_match_new(NULL, 0, 0, 0);
+ expr_match_add(matches, m);
+ } else {
+ /* No match. */
+ }
+ break;
+ }
+ return n_conjs;
+}
+
+/* Returns true if 'expr' honors the invariants for expressions (see the large
+ * comment above "struct expr" in expr.h), false otherwise. */
+bool
+expr_honors_invariants(const struct expr *expr)
+{
+ const struct expr *sub;
+
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ if (expr->cmp.symbol->width) {
+ for (int i = 0; i < ARRAY_SIZE(expr->cmp.value.be64); i++) {
+ if (expr->cmp.value.be64[i] & ~expr->cmp.mask.be64[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+
+ case EXPR_T_AND:
+ case EXPR_T_OR:
+ if (list_is_short(&expr->andor)) {
+ return false;
+ }
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (sub->type == expr->type || !expr_honors_invariants(sub)) {
+ return false;
+ }
+ }
+ return true;
+
+ case EXPR_T_BOOLEAN:
+ return true;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static bool
+expr_is_normalized_and(const struct expr *expr)
+{
+ /* XXX should also check that no symbol is repeated. */
+ const struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (!expr_is_cmp(sub)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* Returns true if 'expr' is in the form returned by expr_normalize(), false
+ * otherwise. */
+bool
+expr_is_normalized(const struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return true;
+
+ case EXPR_T_AND:
+ return expr_is_normalized_and(expr);
+
+ case EXPR_T_OR:
+ if (!expr_is_cmp(expr)) {
+ const struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (!expr_is_cmp(sub) && !expr_is_normalized_and(sub)) {
+ return false;
+ }
+ }
+ }
+ return true;
+
+ case EXPR_T_BOOLEAN:
+ return true;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h
new file mode 100644
index 000000000..ab13c1b8e
--- /dev/null
+++ b/ovn/lib/expr.h
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_EXPR_H
+#define OVN_EXPR_H 1
+
+/* OVN matching expression tree
+ * ============================
+ *
+ * The data structures here form an abstract expression tree for matching
+ * expressions in OVN.
+ *
+ * The abstract syntax tree representation of a matching expression is one of:
+ *
+ * - A Boolean literal ("true" or "false").
+ *
+ * - A comparison of a field (or part of a field) against a constant
+ * with one of the operators == != < <= > >=.
+ *
+ * - The logical AND or OR of two or more matching expressions.
+ *
+ * Literals and comparisons are called "terminal" nodes, logical AND and OR
+ * nodes are "nonterminal" nodes.
+ *
+ * The syntax for expressions includes a few other concepts that are not part
+ * of the abstract syntax tree. In these examples, x is a field, a, b, and c
+ * are constants, and e1 and e2 are arbitrary expressions:
+ *
+ * - Logical NOT. The parser implements NOT by inverting the sense of the
+ * operand: !(x == a) becomes x != a, !(e1 && e2) becomes !e1 || !e2, and
+ * so on.
+ *
+ * - Set membership. The parser translates x == {a, b, c} into
+ * x == a || x == b || x == c.
+ *
+ * - Reversed comparisons. The parser translates a < x into x > a.
+ *
+ * - Range expressions. The parser translates a < x < b into
+ * x > a && x < b.
+ */
+
+#include "classifier.h"
+#include "lex.h"
+#include "hmap.h"
+#include "list.h"
+#include "match.h"
+#include "meta-flow.h"
+
+struct ds;
+struct shash;
+
+/* "Measurement level" of a field. See "Level of Measurement" in the large
+ * comment on struct expr_symbol below for more information. */
+enum expr_level {
+ EXPR_L_NOMINAL,
+
+ /* Boolean values are nominal, however because of their simple nature OVN
+ * can allow both equality and inequality tests on them. */
+ EXPR_L_BOOLEAN,
+
+ /* Ordinal values can at least be ordered on a scale. OVN allows equality
+ * and inequality and relational tests on ordinal values. These are the
+ * fields on which OVS allows bitwise matching. */
+ EXPR_L_ORDINAL
+};
+
+const char *expr_level_to_string(enum expr_level);
+
+/* A symbol.
+ *
+ *
+ * Name
+ * ====
+ *
+ * Every symbol must have a name. To be useful, the name must satisfy the
+ * lexer's syntax for an identifier.
+ *
+ *
+ * Width
+ * =====
+ *
+ * Every symbol has a width. For integer symbols, this is the number of bits
+ * in the value; for string symbols, this is 0.
+ *
+ *
+ * Types
+ * =====
+ *
+ * There are three kinds of symbols:
+ *
+ * Fields:
+ *
+ * One might, for example, define a field named "vlan.tci" to refer to
+ * MFF_VLAN_TCI. For integer fields, 'field' specifies the referent; for
+ * string fields, 'field' is NULL.
+ *
+ * 'expansion' is NULL.
+ *
+ * Integer fields can be nominal or ordinal (see below). String fields are
+ * always nominal.
+ *
+ * Subfields:
+ *
+ * 'expansion' is a string that specifies a subfield of some larger field,
+ * e.g. "vlan.tci[0..11]" for a field that represents a VLAN VID.
+ *
+ * 'field' is NULL.
+ *
+ * Only ordinal fields (see below) may have subfields, and subfields are
+ * always ordinal.
+ *
+ * Predicates:
+ *
+ * A predicate is an arbitrary Boolean expression that can be used in an
+ * expression much like a 1-bit field. 'expansion' specifies the Boolean
+ * expression, e.g. "ip4" might expand to "eth.type == 0x800". The
+ * expansion of a predicate might refer to other predicates, e.g. "icmp4"
+ * might expand to "ip4 && ip4.proto == 1".
+ *
+ * 'field' is NULL.
+ *
+ * A predicate whose expansion refers to any nominal field or predicate
+ * (see below) is nominal; other predicates have Boolean level of
+ * measurement.
+ *
+ *
+ * Level of Measurement
+ * ====================
+ *
+ * See http://en.wikipedia.org/wiki/Level_of_measurement for the statistical
+ * concept on which this classification is based. There are three levels:
+ *
+ * Ordinal:
+ *
+ * In statistics, ordinal values can be ordered on a scale. Here, we
+ * consider a field (or subfield) to be ordinal if its bits can be examined
+ * individually. This is true for the OpenFlow fields that OpenFlow or
+ * Open vSwitch makes "maskable".
+ *
+ * OVN supports all the usual arithmetic relations (== != < <= > >=) on
+ * ordinal fields and their subfields, because all of these can be
+ * implemented as collections of bitwise tests.
+ *
+ * Nominal:
+ *
+ * In statistics, nominal values cannot be usefully compared except for
+ * equality. This is true of OpenFlow port numbers, Ethernet types, and IP
+ * protocols are examples: all of these are just identifiers assigned
+ * arbitrarily with no deeper meaning. In OpenFlow and Open vSwitch, bits
+ * in these fields generally aren't individually addressable.
+ *
+ * OVN only supports arithmetic tests for equality on nominal fields,
+ * because OpenFlow and Open vSwitch provide no way for a flow to
+ * efficiently implement other comparisons on them. (A test for inequality
+ * can be sort of built out of two flows with different priorities, but OVN
+ * matching expressions always generate flows with a single priority.)
+ *
+ * String fields are always nominal.
+ *
+ * Boolean:
+ *
+ * A nominal field that has only two values, 0 and 1, is somewhat
+ * exceptional, since it is easy to support both equality and inequality
+ * tests on such a field: either one can be implemented as a test for 0 or
+ * 1.
+ *
+ * Only predicates (see above) have a Boolean level of measurement.
+ *
+ * This isn't a standard level of measurement.
+ *
+ *
+ * Prerequisites
+ * =============
+ *
+ * Any symbol can have prerequisites, which are specified as a string giving an
+ * additional expression that must be true whenever the symbol is referenced.
+ * For example, the "icmp4.type" symbol might have prerequisite "icmp4", which
+ * would cause an expression "icmp4.type == 0" to be interpreted as "icmp4.type
+ * == 0 && icmp4", which would in turn expand to "icmp4.type == 0 && eth.type
+ * == 0x800 && ip4.proto == 1" (assuming "icmp4" is a predicate defined as
+ * suggested under "Types" above).
+ *
+ *
+ * Crossproducting
+ * ===============
+ *
+ * Ordinarily OVN is willing to consider using any field as a dimension in the
+ * Open vSwitch "conjunctive match" extension (see ovs-ofctl(8)). However,
+ * some fields can't actually be used that way because they are necessary as
+ * prerequisites. For example, from an expression like "tcp.src == {1,2,3}
+ * && tcp.dst == {4,5,6}", OVN might naturally generate flows like this:
+ *
+ * conj_id=1,actions=...
+ * ip,actions=conjunction(1,1/3)
+ * ip6,actions=conjunction(1,1/3)
+ * tp_src=1,actions=conjunction(1,2/3)
+ * tp_src=2,actions=conjunction(1,2/3)
+ * tp_src=2,actions=conjunction(1,2/3)
+ * tp_dst=4,actions=conjunction(1,3/3)
+ * tp_dst=5,actions=conjunction(1,3/3)
+ * tp_dst=6,actions=conjunction(1,3/3)
+ *
+ * but that's not valid because any flow that matches on tp_src or tp_dst must
+ * also match on either ip or ip6. Thus, one would mark eth.type as "must
+ * crossproduct", to force generating flows like this:
+ *
+ * conj_id=1,actions=...
+ * ip,tp_src=1,actions=conjunction(1,1/2)
+ * ip,tp_src=2,actions=conjunction(1,1/2)
+ * ip,tp_src=2,actions=conjunction(1,1/2)
+ * ip6,tp_src=1,actions=conjunction(1,1/2)
+ * ip6,tp_src=2,actions=conjunction(1,1/2)
+ * ip6,tp_src=2,actions=conjunction(1,1/2)
+ * ip,tp_dst=4,actions=conjunction(1,2/2)
+ * ip,tp_dst=5,actions=conjunction(1,2/2)
+ * ip,tp_dst=6,actions=conjunction(1,2/2)
+ * ip6,tp_dst=4,actions=conjunction(1,2/2)
+ * ip6,tp_dst=5,actions=conjunction(1,2/2)
+ * ip6,tp_dst=6,actions=conjunction(1,2/2)
+ *
+ * which are acceptable.
+ */
+struct expr_symbol {
+ char *name;
+ int width;
+
+ const struct mf_field *field;
+ char *expansion;
+
+ enum expr_level level;
+
+ char *prereqs;
+ bool must_crossproduct;
+};
+
+struct expr_symbol *expr_symtab_add_field(struct shash *symtab,
+ const char *name, enum mf_field_id,
+ const char *prereqs,
+ bool must_crossproduct);
+struct expr_symbol *expr_symtab_add_subfield(struct shash *symtab,
+ const char *name,
+ const char *prereqs,
+ const char *subfield);
+struct expr_symbol *expr_symtab_add_string(struct shash *symtab,
+ const char *name,
+ const char *prereqs);
+struct expr_symbol *expr_symtab_add_predicate(struct shash *symtab,
+ const char *name,
+ const char *expansion);
+void expr_symtab_destroy(struct shash *symtab);
+
+/* Expression type. */
+enum expr_type {
+ EXPR_T_CMP, /* Compare symbol with constant. */
+ EXPR_T_AND, /* Logical AND of 2 or more subexpressions. */
+ EXPR_T_OR, /* Logical OR of 2 or more subexpressions. */
+ EXPR_T_BOOLEAN, /* True or false constant. */
+};
+
+/* Relational operator. */
+enum expr_relop {
+ EXPR_R_EQ, /* == */
+ EXPR_R_NE, /* != */
+ EXPR_R_LT, /* < */
+ EXPR_R_LE, /* <= */
+ EXPR_R_GT, /* > */
+ EXPR_R_GE, /* >= */
+};
+const char *expr_relop_to_string(enum expr_relop);
+bool expr_relop_from_token(enum lex_type type, enum expr_relop *relop);
+
+/* An abstract syntax tree for a matching expression.
+ *
+ * The expression code maintains and relies on a few important invariants:
+ *
+ * - An EXPR_T_AND or EXPR_T_OR node never has a child of the same type.
+ * (Any such children could be merged into their parent.) A node may
+ * have grandchildren of its own type.
+ *
+ * As a consequence, every nonterminal node at the same distance from the
+ * root of the root has the same type.
+ *
+ * - EXPR_T_AND and EXPR_T_OR nodes must have at least two children.
+ *
+ * - An EXPR_T_CMP node always has a nonzero mask, and never has a 1-bit
+ * in its value in a position where the mask is a 0-bit.
+ *
+ * The expr_honors_invariants() function can check invariants. */
+struct expr {
+ struct ovs_list node; /* In parent EXPR_T_AND or EXPR_T_OR if any. */
+ enum expr_type type; /* Expression type. */
+
+ union {
+ /* EXPR_T_CMP.
+ *
+ * The symbol is on the left, e.g. "field < constant". */
+ struct {
+ const struct expr_symbol *symbol;
+ enum expr_relop relop;
+
+ union {
+ char *string;
+ struct {
+ union mf_subvalue value;
+ union mf_subvalue mask;
+ };
+ };
+ } cmp;
+
+ /* EXPR_T_AND, EXPR_T_OR. */
+ struct ovs_list andor;
+
+ /* EXPR_T_BOOLEAN. */
+ bool boolean;
+ };
+};
+
+struct expr *expr_create_boolean(bool b);
+struct expr *expr_create_andor(enum expr_type);
+struct expr *expr_combine(enum expr_type, struct expr *a, struct expr *b);
+
+static inline struct expr *
+expr_from_node(const struct ovs_list *node)
+{
+ return CONTAINER_OF(node, struct expr, node);
+}
+
+void expr_format(const struct expr *, struct ds *);
+void expr_print(const struct expr *);
+struct expr *expr_parse(struct lexer *, const struct shash *, char **errorp);
+struct expr *expr_parse_string(const char *, const struct shash *,
+ char **errorp);
+
+struct expr *expr_clone(struct expr *);
+void expr_destroy(struct expr *);
+
+struct expr *expr_annotate(struct expr *, const struct shash *, char **errorp);
+struct expr *expr_simplify(struct expr *);
+struct expr *expr_normalize(struct expr *);
+
+bool expr_honors_invariants(const struct expr *);
+bool expr_is_simplified(const struct expr *);
+bool expr_is_normalized(const struct expr *);
+
+struct expr_match {
+ struct hmap_node hmap_node;
+ struct match match;
+ struct cls_conjunction *conjunctions;
+ size_t n, allocated;
+};
+
+uint32_t expr_to_matches(const struct expr *, struct hmap *);
+
+#endif /* ovn/expr.h */
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 978ce11cf..2c6017b83 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -246,135 +246,268 @@
- Matching expressions have two important kinds of primary expression:
- fields and constants. A field names a piece of
- data or metadata. The supported fields are:
+ The most important components of match expression are
+ comparisons between symbols and
+ constants, e.g. ip4.dst == 192.168.0.1
,
+ ip.proto == 6
, arp.op == 1
, eth.type ==
+ 0x800
. The logical AND operator &&
and
+ logical OR operator ||
can combine comparisons into a
+ larger expression.
-
- -
-
metadata
reg0
... reg7
- xreg0
... xreg3
-
- inport
outport
queue
- eth.src
eth.dst
eth.type
- vlan.tci
vlan.vid
vlan.pcp
vlan.present
- ip.proto
ip.dscp
ip.ecn
ip.ttl
ip.frag
- ip4.src
ip4.dst
- ip6.src
ip6.dst
ip6.label
- arp.op
arp.spa
arp.tpa
arp.sha
arp.tha
- tcp.src
tcp.dst
tcp.flags
- udp.src
udp.dst
- sctp.src
sctp.dst
- icmp4.type
icmp4.code
- icmp6.type
icmp6.code
- nd.target
nd.sll
nd.tll
-
-
- Subfields may be addressed using a []
suffix,
- e.g. tcp.src[0..7]
refers to the low 8 bits of the TCP
- source port. A subfield may be used in any context a field is allowed.
+ Matching expressions also support parentheses for grouping, the logical
+ NOT prefix operator !
, and literals 0
and
+ 1
to express ``false'' or ``true,'' respectively. The
+ latter is useful by itself as a catch-all expression that matches every
+ packet.
-
- Some fields have prerequisites. OVN implicitly adds clauses to satisfy
- these. For example, arp.op == 1
is equivalent to
- eth.type == 0x0806 && arp.op == 1
, and
- tcp.src == 80
is equivalent to (eth.type == 0x0800
- || eth.type == 0x86dd) && ip.proto == 6 && tcp.src ==
- 80
.
-
+ Symbols
- Most fields have integer values. Integer constants may be expressed in
- several forms: decimal integers, hexadecimal integers prefixed by
- 0x
, dotted-quad IPv4 addresses, IPv6 addresses in their
- standard forms, and as Ethernet addresses as colon-separated hex
- digits. A constant in any of these forms may be followed by a slash
- and a second constant (the mask) in the same form, to form a masked
- constant. IPv4 and IPv6 masks may be given as integers, to express
- CIDR prefixes.
+ Type. Symbols have integer or string
+ type. Integer symbols have a width in bits.
- The inport
and outport
fields have string
- values. The useful values are names from
- the and table.
+ Kinds. There are three kinds of symbols:
+
+ -
+
+ Fields. A field symbol represents a packet header or
+ metadata field. For example, a field
+ named vlan.tci
might represent the VLAN TCI field in a
+ packet.
+
+
+
+ A field symbol can have integer or string type. Integer fields can
+ be nominal or ordinal (see Level of Measurement,
+ below).
+
+
+
+ -
+
+ Subfields. A subfield represents a subset of bits from
+ a larger field. For example, a field vlan.vid
might
+ be defined as an alias for vlan.tci[0..11]
. Subfields
+ are provided for syntactic convenience, because it is always
+ possible to instead refer to a subset of bits from a field
+ directly.
+
+
+
+ Only ordinal fields (see Level of Measurement,
+ below) may have subfields. Subfields are always ordinal.
+
+
+
+ -
+
+ Predicates. A predicate is shorthand for a Boolean
+ expression. Predicates may be used much like 1-bit fields. For
+ example, ip4
might expand to eth.type ==
+ 0x800
. Predicates are provided for syntactic convenience,
+ because it is always possible to instead specify the underlying
+ expression directly.
+
+
+
+ A predicate whose expansion refers to any nominal field or
+ predicate (see Level of Measurement, below) is nominal;
+ other predicates have Boolean level of measurement.
+
+
+
+
- The available operators, from highest to lowest precedence, are:
+ Level of Measurement. See
+ http://en.wikipedia.org/wiki/Level_of_measurement for the statistical
+ concept on which this classification is based. There are three
+ levels:
- ()
- == != < <= > >= in not in
- !
- &&
- ||
+ -
+
+ Ordinal. In statistics, ordinal values can be ordered
+ on a scale. OVN considers a field (or subfield) to be ordinal if
+ its bits can be examined individually. This is true for the
+ OpenFlow fields that OpenFlow or Open vSwitch makes ``maskable.''
+
+
+
+ Any use of a nominal field may specify a single bit or a range of
+ bits, e.g. vlan.tci[13..15]
refers to the PCP field
+ within the VLAN TCI, and eth.dst[40]
refers to the
+ multicast bit in the Ethernet destination address.
+
+
+
+ OVN supports all the usual arithmetic relations (==
,
+ !=
, <
, <=
,
+ >
, and >=
) on ordinal fields and
+ their subfields, because OVN can implement these in OpenFlow and
+ Open vSwitch as collections of bitwise tests.
+
+
+
+ -
+
+ Nominal. In statistics, nominal values cannot be
+ usefully compared except for equality. This is true of OpenFlow
+ port numbers, Ethernet types, and IP protocols are examples: all of
+ these are just identifiers assigned arbitrarily with no deeper
+ meaning. In OpenFlow and Open vSwitch, bits in these fields
+ generally aren't individually addressable.
+
+
+
+ OVN only supports arithmetic tests for equality on nominal fields,
+ because OpenFlow and Open vSwitch provide no way for a flow to
+ efficiently implement other comparisons on them. (A test for
+ inequality can be sort of built out of two flows with different
+ priorities, but OVN matching expressions always generate flows with
+ a single priority.)
+
+
+
+ String fields are always nominal.
+
+
+
+ -
+
+ Boolean. A nominal field that has only two values, 0
+ and 1, is somewhat exceptional, since it is easy to support both
+ equality and inequality tests on such a field: either one can be
+ implemented as a test for 0 or 1.
+
+
+
+ Only predicates (see above) have a Boolean level of measurement.
+
+
+
+ This isn't a standard level of measurement.
+
+
- The ()
operator is used for grouping.
+ Prerequisites. Any symbol can have prerequisites, which are
+ additional condition implied by the use of the symbol. For example,
+ For example, icmp4.type
symbol might have prerequisite
+ icmp4
, which would cause an expression icmp4.type ==
+ 0
to be interpreted as icmp4.type == 0 &&
+ icmp4
, which would in turn expand to icmp4.type == 0
+ && eth.type == 0x800 && ip4.proto == 1
(assuming
+ icmp4
is a predicate defined as suggested under
+ Types above).
+ Relational operators
+
- The equality operator ==
is the most important operator.
- Its operands must be a field and an optionally masked constant, in
- either order. The ==
operator yields true when the
- field's value equals the constant's value for all the bits included in
- the mask. The ==
operator translates simply and naturally
- to OpenFlow.
+ All of the standard relational operators ==
,
+ !=
, <
, <=
,
+ >
, and >=
are supported. Nominal
+ fields support only ==
and !=
, and only in a
+ positive sense when outer !
are taken into account,
+ e.g. given string field inport
, inport ==
+ "eth0"
and !(inport != "eth0")
are acceptable, but
+ not inport != "eth0"
.
- The inequality operator !=
yields the inverse of
- ==
but its syntax and use are the same. Implementation of
- the inequality operator is expensive.
+ The implementation of ==
(or !=
when it is
+ negated), is more efficient than that of the other relational
+ operators.
+ Constants
+
- The relational operators are <, <=, >, and >=. Their
- operands must be a field and a constant, in either order; the constant
- must not be masked. These operators are most commonly useful for L4
- ports, e.g. tcp.src < 1024
. Implementation of the
- relational operators is expensive.
+ Integer constants may be expressed in decimal, hexadecimal prefixed by
+ 0x
, or as dotted-quad IPv4 addresses, IPv6 addresses in
+ their standard forms, or Ethernet addresses as colon-separated hex
+ digits. A constant in any of these forms may be followed by a slash
+ and a second constant (the mask) in the same form, to form a masked
+ constant. IPv4 and IPv6 masks may be given as integers, to express
+ CIDR prefixes.
+
+
+
+ String constants have the same syntax as quoted strings in JSON (thus,
+ they are Unicode strings). String constants are used for naming
+ logical ports. Thus, the useful values are names from the and
+ table.
- The set membership operator in
, with syntax
- ``field in { constant1,
- constant2,
... }
'', is syntactic sugar
- for ``(field == constant1 ||
- field == constant2 ||
...)
.
- Conversely, ``field not in { constant1,
- constant2,
... }
'' is syntactic sugar
- for ``(field != constant1 &&
+ Some operators support sets of constants written inside curly braces
+ {
... }
. Commas between elements of a set,
+ and after the last elements, are optional. With ==
,
+ ``field == { constant1,
+ constant2,
... }
'' is syntactic sugar
+ for ``field == constant1 ||
+ field == constant2 ||
...
.
+ Similarly, ``field != { constant1,
+ constant2,
... }
'' is equivalent to
+ ``field != constant1 &&
field != constant2 &&
-
...)
''.
+
...
''.
+ Miscellaneous
+
- The unary prefix operator !
yields its operand's inverse.
+ Comparisons may name the symbol or the constant first,
+ e.g. tcp.src == 80
and 80 == tcp.src
are both
+ acceptable.
- The logical AND operator &&
yields true only if
- both of its operands are true.
+ Tests for a range may be expressed using a syntax like 1024 <=
+ tcp.src <= 49151
, which is equivalent to 1024 <=
+ tcp.src && tcp.src <= 49151
.
- The logical OR operator ||
yields true if at least one of
- its operands is true.
+ For a one-bit field or predicate, a mention of its name is equivalent
+ to symobl == 1
, e.g. vlan.present
+ is equivalent to vlan.present == 1
. The same is true for
+ one-bit subfields, e.g. vlan.tci[12]
. There is no
+ technical limitation to implementing the same for ordinal fields of all
+ widths, but the implementation is expensive enough that the syntax
+ parser requires writing an explicit comparison against zero to make
+ mistakes less likely, e.g. in tcp.src != 0
the comparison
+ against 0 is required.
- Finally, the keywords true
and false
may also
- be used in matching expressions. true
is useful by itself
- as a catch-all expression that matches every packet.
+ Operator precedence is as shown below, from highest to lowest.
+ There are two exceptions where parentheses are required even though the
+ table would suggest that they are not: &&
and
+ ||
require parentheses when used together, and
+ !
requires parentheses when applied to a relational
+ expression. Thus, in (eth.type == 0x800 || eth.type == 0x86dd)
+ && ip.proto == 6
or !(arp.op == 1)
, the
+ parentheses are mandatory.
+
+ ()
+ == != < <= > >=
+ !
+ && ||
+
+
Comments may be introduced by //
, which extends
to the next new-line. Comments within a line may be bracketed by
@@ -382,12 +515,28 @@
supported.
-
- (The above is pretty ambitious. It probably makes sense to initially
- implement only a subset of this specification. The full specification
- is written out mainly to get an idea of what a fully general matching
- expression language could include.)
-
+ Symbols
+
+
+ -
+
metadata
reg0
... reg7
+ xreg0
... xreg3
+
+ inport
outport
queue
+ eth.src
eth.dst
eth.type
+ vlan.tci
vlan.vid
vlan.pcp
vlan.present
+ ip.proto
ip.dscp
ip.ecn
ip.ttl
ip.frag
+ ip4.src
ip4.dst
+ ip6.src
ip6.dst
ip6.label
+ arp.op
arp.spa
arp.tpa
arp.sha
arp.tha
+ tcp.src
tcp.dst
tcp.flags
+ udp.src
udp.dst
+ sctp.src
sctp.dst
+ icmp4.type
icmp4.code
+ icmp6.type
icmp6.code
+ nd.target
nd.sll
nd.tll
+
+
@@ -418,24 +567,24 @@
- learn
+ learn
- conntrack
+ conntrack
- with(field=value) { action,
... }
- - execute actions with temporary changes to fields
+ with(field=value) { action,
... }
+ - execute actions with temporary changes to fields
- dec_ttl { action,
... } { action;
...}
- -
- decrement TTL; execute first set of actions if
- successful, second set if TTL decrement fails
-
+ dec_ttl { action,
... } { action;
...}
+ -
+ decrement TTL; execute first set of actions if
+ successful, second set if TTL decrement fails
+
- icmp_reply { action,
... }
- - generate ICMP reply from packet, execute actions
+ icmp_reply { action,
... }
+ - generate ICMP reply from packet, execute actions
- arp { action,
... }
- - generate ARP from packet, execute actions
+ arp { action,
... }
+ - generate ARP from packet, execute actions
diff --git a/tests/ovn.at b/tests/ovn.at
index d28a684d9..fa89cbe5d 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -93,3 +93,256 @@ sed 's/ =>.*//' test-cases.txt > input.txt
sed 's/.* => //' test-cases.txt > expout
AT_CHECK([ovstest test-ovn lex < input.txt], [0], [expout])
AT_CLEANUP
+
+AT_SETUP([ovn -- expression parser])
+dnl For lines without =>, input and expected output are identical.
+dnl For lines with =>, input precedes => and expected output follows =>.
+AT_DATA([test-cases.txt], [[
+eth.type == 0x800
+eth.type==0x800 => eth.type == 0x800
+eth.type[0..15] == 0x800 => eth.type == 0x800
+
+vlan.present
+vlan.present == 1 => vlan.present
+!(vlan.present == 0) => vlan.present
+!(vlan.present != 1) => vlan.present
+!vlan.present
+vlan.present == 0 => !vlan.present
+vlan.present != 1 => !vlan.present
+!(vlan.present == 1) => !vlan.present
+!(vlan.present != 0) => !vlan.present
+
+eth.dst[0]
+eth.dst[0] == 1 => eth.dst[0]
+eth.dst[0] != 0 => eth.dst[0]
+!(eth.dst[0] == 0) => eth.dst[0]
+!(eth.dst[0] != 1) => eth.dst[0]
+
+!eth.dst[0]
+eth.dst[0] == 0 => !eth.dst[0]
+eth.dst[0] != 1 => !eth.dst[0]
+!(eth.dst[0] == 1) => !eth.dst[0]
+!(eth.dst[0] != 0) => !eth.dst[0]
+
+vlan.tci[12..15] == 0x3
+vlan.tci == 0x3000/0xf000 => vlan.tci[12..15] == 0x3
+vlan.tci[12..15] != 0x3
+vlan.tci != 0x3000/0xf000 => vlan.tci[12..15] != 0x3
+
+!vlan.pcp => vlan.pcp == 0
+!(vlan.pcp) => vlan.pcp == 0
+vlan.pcp == 0x4
+vlan.pcp != 0x4
+vlan.pcp > 0x4
+vlan.pcp >= 0x4
+vlan.pcp < 0x4
+vlan.pcp <= 0x4
+!(vlan.pcp != 0x4) => vlan.pcp == 0x4
+!(vlan.pcp == 0x4) => vlan.pcp != 0x4
+!(vlan.pcp <= 0x4) => vlan.pcp > 0x4
+!(vlan.pcp < 0x4) => vlan.pcp >= 0x4
+!(vlan.pcp >= 0x4) => vlan.pcp < 0x4
+!(vlan.pcp > 0x4) => vlan.pcp <= 0x4
+0x4 == vlan.pcp => vlan.pcp == 0x4
+0x4 != vlan.pcp => vlan.pcp != 0x4
+0x4 < vlan.pcp => vlan.pcp > 0x4
+0x4 <= vlan.pcp => vlan.pcp >= 0x4
+0x4 > vlan.pcp => vlan.pcp < 0x4
+0x4 >= vlan.pcp => vlan.pcp <= 0x4
+!(0x4 != vlan.pcp) => vlan.pcp == 0x4
+!(0x4 == vlan.pcp) => vlan.pcp != 0x4
+!(0x4 >= vlan.pcp) => vlan.pcp > 0x4
+!(0x4 > vlan.pcp) => vlan.pcp >= 0x4
+!(0x4 <= vlan.pcp) => vlan.pcp < 0x4
+!(0x4 < vlan.pcp) => vlan.pcp <= 0x4
+
+1 < vlan.pcp < 4 => vlan.pcp > 0x1 && vlan.pcp < 0x4
+1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4
+1 < vlan.pcp <= 4 => vlan.pcp > 0x1 && vlan.pcp <= 0x4
+1 <= vlan.pcp < 4 => vlan.pcp >= 0x1 && vlan.pcp < 0x4
+1 <= vlan.pcp <= 4 => vlan.pcp >= 0x1 && vlan.pcp <= 0x4
+4 > vlan.pcp > 1 => vlan.pcp < 0x4 && vlan.pcp > 0x1
+4 >= vlan.pcp > 1 => vlan.pcp <= 0x4 && vlan.pcp > 0x1
+4 > vlan.pcp >= 1 => vlan.pcp < 0x4 && vlan.pcp >= 0x1
+4 >= vlan.pcp >= 1 => vlan.pcp <= 0x4 && vlan.pcp >= 0x1
+!(1 < vlan.pcp < 4) => vlan.pcp <= 0x1 || vlan.pcp >= 0x4
+!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4
+!(1 < vlan.pcp <= 4) => vlan.pcp <= 0x1 || vlan.pcp > 0x4
+!(1 <= vlan.pcp < 4) => vlan.pcp < 0x1 || vlan.pcp >= 0x4
+!(1 <= vlan.pcp <= 4) => vlan.pcp < 0x1 || vlan.pcp > 0x4
+!(4 > vlan.pcp > 1) => vlan.pcp >= 0x4 || vlan.pcp <= 0x1
+!(4 >= vlan.pcp > 1) => vlan.pcp > 0x4 || vlan.pcp <= 0x1
+!(4 > vlan.pcp >= 1) => vlan.pcp >= 0x4 || vlan.pcp < 0x1
+!(4 >= vlan.pcp >= 1) => vlan.pcp > 0x4 || vlan.pcp < 0x1
+
+vlan.pcp == {1, 2, 3, 4} => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4
+vlan.pcp == 1 || ((vlan.pcp == 2 || vlan.pcp == 3) || vlan.pcp == 4) => vlan.pcp == 0x1 || vlan.pcp == 0x2 || vlan.pcp == 0x3 || vlan.pcp == 0x4
+
+vlan.pcp != {1, 2, 3, 4} => vlan.pcp != 0x1 && vlan.pcp != 0x2 && vlan.pcp != 0x3 && vlan.pcp != 0x4
+vlan.pcp == 1 && ((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && vlan.pcp == 0x2 && vlan.pcp == 0x3 && vlan.pcp == 0x4
+
+vlan.pcp == 1 && !((vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3 || vlan.pcp != 0x4)
+vlan.pcp == 1 && (!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && (vlan.pcp != 0x2 || vlan.pcp != 0x3) && vlan.pcp == 0x4
+vlan.pcp == 1 && !(!(vlan.pcp == 2 && vlan.pcp == 3) && vlan.pcp == 4) => vlan.pcp == 0x1 && ((vlan.pcp == 0x2 && vlan.pcp == 0x3) || vlan.pcp != 0x4)
+
+ip4.src == {10.0.0.0/8, 192.168.0.0/16, 172.16.20.0/24, 8.8.8.8} => ip4.src[24..31] == 0xa || ip4.src[16..31] == 0xc0a8 || ip4.src[8..31] == 0xac1014 || ip4.src == 0x8080808
+ip6.src == ::1 => ip6.src == 0x1
+
+ip4.src == 1.2.3.4 => ip4.src == 0x1020304
+ip4.src == ::1.2.3.4/::ffff:ffff => ip4.src == 0x1020304
+ip6.src == ::1 => ip6.src == 0x1
+
+1
+0
+!1 => 0
+!0 => 1
+
+inport == "eth0"
+!(inport != "eth0") => inport == "eth0"
+
+ip4.src == "eth0" => Can't compare integer field ip4.src to string constant.
+inport == 1 => Can't compare string field inport to integer constant.
+
+ip4.src > {1, 2, 3} => Only == and != operators may be used with value sets.
+eth.type > 0x800 => Only == and != operators may be used with nominal field eth.type.
+vlan.present > 0 => Only == and != operators may be used with Boolean field vlan.present.
+
+inport != "eth0" => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account).
+!(inport == "eth0") => Nominal field inport may only be tested for equality (taking enclosing `!' operators into account).
+eth.type != 0x800 => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account).
+!(eth.type == 0x800) => Nominal field eth.type may only be tested for equality (taking enclosing `!' operators into account).
+
+123 == 123 => Syntax error at `123' expecting field name.
+
+123 == xyzzy => Syntax error at `xyzzy' expecting field name.
+xyzzy == 1 => Syntax error at `xyzzy' expecting field name.
+
+inport[1] == 1 => Cannot select subfield of string field inport.
+
+eth.type[] == 1 => Syntax error at `@:>@' expecting small integer.
+eth.type[::1] == 1 => Syntax error at `::1' expecting small integer.
+eth.type[18446744073709551615] == 1 => Syntax error at `18446744073709551615' expecting small integer.
+
+eth.type[5!] => Syntax error at `!' expecting `@:>@'.
+
+eth.type[5..1] => Invalid bit range 5 to 1.
+
+eth.type[12..16] => Cannot select bits 12 to 16 of 16-bit field eth.type.
+
+eth.type[10] == 1 => Cannot select subfield of nominal field eth.type.
+
+eth.type => Explicit `!= 0' is required for inequality test of multibit field against 0.
+
+!(!(vlan.pcp)) => Explicit `!= 0' is required for inequality test of multibit field against 0.
+
+123 => Syntax error at end of input expecting relational operator.
+
+123 x => Syntax error at `x' expecting relational operator.
+
+{1, "eth0"} => Syntax error at `"eth0"' expecting integer.
+
+eth.type == xyzzy => Syntax error at `xyzzy' expecting constant.
+
+(1 x) => Syntax error at `x' expecting `)'.
+
+!0x800 != eth.type => Missing parentheses around operand of !.
+
+eth.type == 0x800 || eth.type == 0x86dd && ip.proto == 17 => && and || must be parenthesized when used together.
+
+eth.dst == {} => Syntax error at `}' expecting constant.
+
+eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff => Only == and != operators may be used with masked constants. Consider using subfields instead (e.g. eth.src[0..15] > 0x1111 in place of eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff).
+
+ip4.src == ::1 => Cannot compare 128-bit constant against 32-bit field ip4.src.
+
+1 == eth.type == 2 => Range expressions must have the form `x < field < y' or `x > field > y', with each `<' optionally replaced by `<=' or `>' by `>=').
+]])
+sed 's/ =>.*//' test-cases.txt > input.txt
+sed 's/.* => //' test-cases.txt > expout
+AT_CHECK([ovstest test-ovn parse-expr < input.txt], [0], [expout])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression annotation])
+dnl Input precedes =>, expected output follows =>.
+AT_DATA([test-cases.txt], [[
+ip4.src == 1.2.3.4 => ip4.src == 0x1020304 && eth.type == 0x800
+ip4.src != 1.2.3.4 => ip4.src != 0x1020304 && eth.type == 0x800
+ip.proto == 123 => ip.proto == 0x7b && (eth.type == 0x800 || eth.type == 0x86dd)
+ip.proto == {123, 234} => (ip.proto == 0x7b && (eth.type == 0x800 || eth.type == 0x86dd)) || (ip.proto == 0xea && (eth.type == 0x800 || eth.type == 0x86dd))
+ip4.src == 1.2.3.4 && ip4.dst == 5.6.7.8 => ip4.src == 0x1020304 && eth.type == 0x800 && ip4.dst == 0x5060708 && eth.type == 0x800
+
+ip => eth.type == 0x800 || eth.type == 0x86dd
+ip == 1 => eth.type == 0x800 || eth.type == 0x86dd
+ip[0] == 1 => eth.type == 0x800 || eth.type == 0x86dd
+ip > 0 => Only == and != operators may be used with nominal field ip.
+!ip => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'.
+ip == 0 => Nominal predicate ip may only be tested positively, e.g. `ip' or `ip == 1' but not `!ip' or `ip == 0'.
+
+vlan.present => vlan.tci[12]
+!vlan.present => !vlan.tci[12]
+
+!vlan.pcp => vlan.tci[13..15] == 0 && vlan.tci[12]
+vlan.pcp == 1 && vlan.vid == 2 => vlan.tci[13..15] == 0x1 && vlan.tci[12] && vlan.tci[0..11] == 0x2 && vlan.tci[12]
+!reg0 && !reg1 && !reg2 && !reg3 => xreg0[32..63] == 0 && xreg0[0..31] == 0 && xreg1[32..63] == 0 && xreg1[0..31] == 0
+
+ip.first_frag => ip.frag[0] && (eth.type == 0x800 || eth.type == 0x86dd) && (!ip.frag[1] || (eth.type != 0x800 && eth.type != 0x86dd))
+!ip.first_frag => !ip.frag[0] || (eth.type != 0x800 && eth.type != 0x86dd) || (ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd))
+ip.later_frag => ip.frag[1] && (eth.type == 0x800 || eth.type == 0x86dd)
+
+bad_prereq != 0 => Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name.
+self_recurse != 0 => Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'.
+mutual_recurse_1 != 0 => Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_1'.
+mutual_recurse_2 != 0 => Error parsing expression `mutual_recurse_1 != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `mutual_recurse_2 != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `mutual_recurse_2'.
+]])
+sed 's/ =>.*//' test-cases.txt > input.txt
+sed 's/.* => //' test-cases.txt > expout
+AT_CHECK([ovstest test-ovn annotate-expr < input.txt], [0], [expout])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression conversion (1)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=convert 1], [0],
+ [Tested converting all 1-terminal expressions with 2 vars each of 3 bits in terms of operators == != < <= > >=.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression conversion (2)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=convert 2], [0],
+ [Tested converting 562 expressions of 2 terminals with 2 vars each of 3 bits in terms of operators == != < <= > >=.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression conversion (3)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=convert --bits=2 3], [0],
+ [Tested converting 57618 expressions of 3 terminals with 2 vars each of 2 bits in terms of operators == != < <= > >=.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression simplification])
+AT_CHECK([ovstest test-ovn exhaustive --operation=simplify --vars=2 3], [0],
+ [Tested simplifying 477138 expressions of 3 terminals with 2 vars each of 3 bits in terms of operators == != < <= > >=.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression normalization (1)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --vars=3 --bits=1 4], [0],
+ [Tested normalizing 1207162 expressions of 4 terminals with 3 vars each of 1 bits in terms of operators == != < <= > >=.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- expression normalization (1)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=normalize --vars=3 --bits=1 --relops='==' 5], [0],
+ [Tested normalizing 368550 expressions of 5 terminals with 3 vars each of 1 bits in terms of operators ==.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- converting expressions to flows (1)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=flow --vars=2 --bits=2 --relops='==' 4], [0],
+ [Tested converting to flows 128282 expressions of 4 terminals with 2 vars each of 2 bits in terms of operators ==.
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- converting expressions to flows (2)])
+AT_CHECK([ovstest test-ovn exhaustive --operation=flow --vars=3 --bits=3 --relops='==' 3], [0],
+ [Tested converting to flows 38394 expressions of 3 terminals with 3 vars each of 3 bits in terms of operators ==.
+])
+AT_CLEANUP
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index b2f183829..a6d8b8000 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -16,15 +16,39 @@
#include
#include "command-line.h"
+#include
#include
+#include
#include "dynamic-string.h"
#include "fatal-signal.h"
#include "match.h"
+#include "ovn/lib/expr.h"
#include "ovn/lib/lex.h"
+#include "ovs-thread.h"
#include "ovstest.h"
+#include "shash.h"
#include "util.h"
#include "openvswitch/vlog.h"
+/* --relops: Bitmap of the relational operators to test, in exhaustive test. */
+static unsigned int test_relops;
+
+/* --vars: Number of variables to test, in exhaustive test. */
+static int test_vars = 2;
+
+/* --bits: Number of bits per variable, in exhaustive test. */
+static int test_bits = 3;
+
+/* --operation: The operation to test, in exhaustive test. */
+static enum { OP_CONVERT, OP_SIMPLIFY, OP_NORMALIZE, OP_FLOW } operation
+ = OP_FLOW;
+
+/* --parallel: Number of parallel processes to use in test. */
+static int test_parallel = 1;
+
+/* -m, --more: Message verbosity */
+static int verbosity;
+
static void
compare_token(const struct lex_token *a, const struct lex_token *b)
{
@@ -102,13 +126,1165 @@ test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
ds_destroy(&output);
}
+static void
+create_symtab(struct shash *symtab)
+{
+ shash_init(symtab);
+
+ expr_symtab_add_string(symtab, "inport", NULL);
+ expr_symtab_add_string(symtab, "outport", NULL);
+
+ expr_symtab_add_field(symtab, "xreg0", MFF_XREG0, NULL, false);
+ expr_symtab_add_field(symtab, "xreg1", MFF_XREG1, NULL, false);
+ expr_symtab_add_field(symtab, "xreg2", MFF_XREG2, NULL, false);
+ expr_symtab_add_field(symtab, "xreg3", MFF_XREG3, NULL, false);
+
+ expr_symtab_add_subfield(symtab, "reg0", NULL, "xreg0[32..63]");
+ expr_symtab_add_subfield(symtab, "reg1", NULL, "xreg0[0..31]");
+ expr_symtab_add_subfield(symtab, "reg2", NULL, "xreg1[32..63]");
+ expr_symtab_add_subfield(symtab, "reg3", NULL, "xreg1[0..31]");
+ expr_symtab_add_subfield(symtab, "reg4", NULL, "xreg2[32..63]");
+ expr_symtab_add_subfield(symtab, "reg5", NULL, "xreg2[0..31]");
+ expr_symtab_add_subfield(symtab, "reg6", NULL, "xreg3[32..63]");
+ expr_symtab_add_subfield(symtab, "reg7", NULL, "xreg3[0..31]");
+
+ expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
+ expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
+ expr_symtab_add_field(symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
+
+ expr_symtab_add_field(symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
+ expr_symtab_add_predicate(symtab, "vlan.present", "vlan.tci[12]");
+ expr_symtab_add_subfield(symtab, "vlan.pcp", "vlan.present",
+ "vlan.tci[13..15]");
+ expr_symtab_add_subfield(symtab, "vlan.vid", "vlan.present",
+ "vlan.tci[0..11]");
+
+ expr_symtab_add_predicate(symtab, "ip4", "eth.type == 0x800");
+ expr_symtab_add_predicate(symtab, "ip6", "eth.type == 0x86dd");
+ expr_symtab_add_predicate(symtab, "ip", "ip4 || ip6");
+ expr_symtab_add_field(symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
+ expr_symtab_add_field(symtab, "ip.dscp", MFF_IP_DSCP, "ip", false);
+ expr_symtab_add_field(symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
+ expr_symtab_add_field(symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
+
+ expr_symtab_add_field(symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
+ expr_symtab_add_field(symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
+
+ expr_symtab_add_predicate(symtab, "icmp4", "ip4 && ip.proto == 1");
+ expr_symtab_add_field(symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
+ false);
+ expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
+ false);
+
+ expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
+ expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
+ expr_symtab_add_field(symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
+
+ expr_symtab_add_predicate(symtab, "icmp6", "ip6 && ip.proto == 58");
+ expr_symtab_add_field(symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
+ true);
+ expr_symtab_add_field(symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
+ true);
+
+ expr_symtab_add_predicate(symtab, "icmp", "icmp4 || icmp6");
+
+ expr_symtab_add_field(symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
+ expr_symtab_add_predicate(symtab, "ip.is_frag", "ip.frag[0]");
+ expr_symtab_add_predicate(symtab, "ip.later_frag", "ip.frag[1]");
+ expr_symtab_add_predicate(symtab, "ip.first_frag", "ip.is_frag && !ip.later_frag");
+
+ expr_symtab_add_predicate(symtab, "arp", "eth.type == 0x806");
+ expr_symtab_add_field(symtab, "arp.op", MFF_ARP_OP, "arp", false);
+ expr_symtab_add_field(symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
+ expr_symtab_add_field(symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
+ expr_symtab_add_field(symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
+ expr_symtab_add_field(symtab, "arp.tha", MFF_ARP_THA, "arp", false);
+
+ expr_symtab_add_predicate(symtab, "nd", "icmp6.type == {135, 136} && icmp6.code == 0");
+ expr_symtab_add_field(symtab, "nd.target", MFF_ND_TARGET, "nd", false);
+ expr_symtab_add_field(symtab, "nd.sll", MFF_ND_SLL,
+ "nd && icmp6.type == 135", false);
+ expr_symtab_add_field(symtab, "nd.tll", MFF_ND_TLL,
+ "nd && icmp6.type == 136", false);
+
+ expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
+ expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
+ expr_symtab_add_field(symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
+ expr_symtab_add_field(symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
+
+ expr_symtab_add_predicate(symtab, "udp", "ip.proto == 17");
+ expr_symtab_add_field(symtab, "udp.src", MFF_UDP_SRC, "udp", false);
+ expr_symtab_add_field(symtab, "udp.dst", MFF_UDP_DST, "udp", false);
+
+ expr_symtab_add_predicate(symtab, "sctp", "ip.proto == 132");
+ expr_symtab_add_field(symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
+ expr_symtab_add_field(symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
+
+ /* For negative testing. */
+ expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
+ expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
+ "self_recurse != 0", false);
+ expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
+ "mutual_recurse_2 != 0", false);
+ expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
+ "mutual_recurse_1 != 0", false);
+}
+
+static void
+test_parse_expr__(int steps)
+{
+ struct shash symtab;
+ struct ds input;
+
+ create_symtab(&symtab);
+ ds_init(&input);
+ while (!ds_get_test_line(&input, stdin)) {
+ struct expr *expr;
+ char *error;
+
+ expr = expr_parse_string(ds_cstr(&input), &symtab, &error);
+ if (!error && steps > 0) {
+ expr = expr_annotate(expr, &symtab, &error);
+ }
+ if (!error) {
+ if (steps > 1) {
+ expr = expr_simplify(expr);
+ }
+ if (steps > 2) {
+ expr = expr_normalize(expr);
+ ovs_assert(expr_is_normalized(expr));
+ }
+ }
+ if (!error) {
+ struct ds output = DS_EMPTY_INITIALIZER;
+ expr_format(expr, &output);
+ puts(ds_cstr(&output));
+ ds_destroy(&output);
+ } else {
+ puts(error);
+ free(error);
+ }
+ expr_destroy(expr);
+ }
+ ds_destroy(&input);
+
+ expr_symtab_destroy(&symtab);
+ shash_destroy(&symtab);
+}
+
+static void
+test_parse_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+ test_parse_expr__(0);
+}
+
+static void
+test_annotate_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+ test_parse_expr__(1);
+}
+
+static void
+test_simplify_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+ test_parse_expr__(2);
+}
+
+static void
+test_normalize_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+ test_parse_expr__(3);
+}
+
+/* Evaluate an expression. */
+
+static bool evaluate_expr(const struct expr *, unsigned int subst, int n_bits);
+
+static bool
+evaluate_andor_expr(const struct expr *expr, unsigned int subst, int n_bits,
+ bool short_circuit)
+{
+ const struct expr *sub;
+
+ LIST_FOR_EACH (sub, node, &expr->andor) {
+ if (evaluate_expr(sub, subst, n_bits) == short_circuit) {
+ return short_circuit;
+ }
+ }
+ return !short_circuit;
+}
+
+static bool
+evaluate_cmp_expr(const struct expr *expr, unsigned int subst, int n_bits)
+{
+ int var_idx = expr->cmp.symbol->name[0] - 'a';
+ unsigned var_mask = (1u << n_bits) - 1;
+ unsigned int arg1 = (subst >> (var_idx * n_bits)) & var_mask;
+ unsigned int arg2 = ntohll(expr->cmp.value.integer);
+ unsigned int mask = ntohll(expr->cmp.mask.integer);
+
+ ovs_assert(!(mask & ~var_mask));
+ ovs_assert(!(arg2 & ~var_mask));
+ ovs_assert(!(arg2 & ~mask));
+
+ arg1 &= mask;
+ switch (expr->cmp.relop) {
+ case EXPR_R_EQ:
+ return arg1 == arg2;
+
+ case EXPR_R_NE:
+ return arg1 != arg2;
+
+ case EXPR_R_LT:
+ return arg1 < arg2;
+
+ case EXPR_R_LE:
+ return arg1 <= arg2;
+
+ case EXPR_R_GT:
+ return arg1 > arg2;
+
+ case EXPR_R_GE:
+ return arg1 >= arg2;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+/* Evaluates 'expr' and returns its Boolean result. 'subst' provides the value
+ * for the variables, which must be 'n_bits' bits each and be named "a", "b",
+ * "c", etc. The value of variable "a" is the least-significant 'n_bits' bits
+ * of 'subst', the value of "b" is the next 'n_bits' bits, and so on. */
+static bool
+evaluate_expr(const struct expr *expr, unsigned int subst, int n_bits)
+{
+ switch (expr->type) {
+ case EXPR_T_CMP:
+ return evaluate_cmp_expr(expr, subst, n_bits);
+
+ case EXPR_T_AND:
+ return evaluate_andor_expr(expr, subst, n_bits, false);
+
+ case EXPR_T_OR:
+ return evaluate_andor_expr(expr, subst, n_bits, true);
+
+ case EXPR_T_BOOLEAN:
+ return expr->boolean;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+}
+
+static void
+test_evaluate_expr(struct ovs_cmdl_context *ctx)
+{
+ int a = atoi(ctx->argv[1]);
+ int b = atoi(ctx->argv[2]);
+ int c = atoi(ctx->argv[3]);
+ unsigned int subst = a | (b << 3) || (c << 6);
+ struct shash symtab;
+ struct ds input;
+
+ shash_init(&symtab);
+ expr_symtab_add_field(&symtab, "xreg0", MFF_XREG0, NULL, false);
+ expr_symtab_add_field(&symtab, "xreg1", MFF_XREG1, NULL, false);
+ expr_symtab_add_field(&symtab, "xreg2", MFF_XREG1, NULL, false);
+ expr_symtab_add_subfield(&symtab, "a", NULL, "xreg0[0..2]");
+ expr_symtab_add_subfield(&symtab, "b", NULL, "xreg1[0..2]");
+ expr_symtab_add_subfield(&symtab, "c", NULL, "xreg2[0..2]");
+
+ ds_init(&input);
+ while (!ds_get_test_line(&input, stdin)) {
+ struct expr *expr;
+ char *error;
+
+ expr = expr_parse_string(ds_cstr(&input), &symtab, &error);
+ if (!error) {
+ expr = expr_annotate(expr, &symtab, &error);
+ }
+ if (!error) {
+ printf("%d\n", evaluate_expr(expr, subst, 3));
+ } else {
+ puts(error);
+ free(error);
+ }
+ expr_destroy(expr);
+ }
+ ds_destroy(&input);
+
+ expr_symtab_destroy(&symtab);
+ shash_destroy(&symtab);
+}
+
+/* Compositions.
+ *
+ * The "compositions" of a positive integer N are all of the ways that one can
+ * add up positive integers to sum to N. For example, the compositions of 3
+ * are 3, 2+1, 1+2, and 1+1+1.
+ *
+ * We use compositions to find all the ways to break up N terms of a Boolean
+ * expression into subexpressions. Suppose we want to generate all expressions
+ * with 3 terms. The compositions of 3 (ignoring 3 itself) provide the
+ * possibilities (x && x) || x, x || (x && x), and x || x || x. (Of course one
+ * can exchange && for || in each case.) One must recursively compose the
+ * sub-expressions whose values are 3 or greater; that is what the "tree shape"
+ * concept later covers.
+ *
+ * To iterate through all compositions of, e.g., 5:
+ *
+ * unsigned int state;
+ * int s[5];
+ * int n;
+ *
+ * for (n = first_composition(ARRAY_SIZE(s), &state, s); n > 0;
+ * n = next_composition(&state, s, n)) {
+ * // Do something with composition 's' with 'n' elements.
+ * }
+ *
+ * Algorithm from D. E. Knuth, _The Art of Computer Programming, Vol. 4A:
+ * Combinatorial Algorithms, Part 1_, section 7.2.1.1, answer to exercise
+ * 12(a).
+ */
+
+/* Begins iteration through the compositions of 'n'. Initializes 's' to the
+ * number of elements in the first composition of 'n' and returns that number
+ * of elements. The first composition in fact is always 'n' itself, so the
+ * return value will be 1.
+ *
+ * Initializes '*state' to some internal state information. The caller must
+ * maintain this state (and 's') for use by next_composition().
+ *
+ * 's' must have room for at least 'n' elements. */
+static int
+first_composition(int n, unsigned int *state, int s[])
+{
+ *state = 0;
+ s[0] = n;
+ return 1;
+}
+
+/* Advances 's', with 'sn' elements, to the next composition and returns the
+ * number of elements in this new composition, or 0 if no compositions are
+ * left. 'state' is the same internal state passed to first_composition(). */
+static int
+next_composition(unsigned int *state, int s[], int sn)
+{
+ int j = sn - 1;
+ if (++*state & 1) {
+ if (s[j] > 1) {
+ s[j]--;
+ s[j + 1] = 1;
+ j++;
+ } else {
+ j--;
+ s[j]++;
+ }
+ } else {
+ if (s[j - 1] > 1) {
+ s[j - 1]--;
+ s[j + 1] = s[j];
+ s[j] = 1;
+ j++;
+ } else {
+ j--;
+ s[j] = s[j + 1];
+ s[j - 1]++;
+ if (!j) {
+ return 0;
+ }
+ }
+ }
+ return j + 1;
+}
+
+static void
+test_composition(struct ovs_cmdl_context *ctx)
+{
+ int n = atoi(ctx->argv[1]);
+ unsigned int state;
+ int s[50];
+
+ for (int sn = first_composition(n, &state, s); sn;
+ sn = next_composition(&state, s, sn)) {
+ for (int i = 0; i < sn; i++) {
+ printf("%d%c", s[i], i == sn - 1 ? '\n' : ' ');
+ }
+ }
+}
+
+/* Tree shapes.
+ *
+ * This code generates all possible Boolean expressions with a specified number
+ * of terms N (equivalent to the number of external nodes in a tree).
+ *
+ * See test_tree_shape() for a simple example. */
+
+/* An array of these structures describes the shape of a tree.
+ *
+ * A single element of struct tree_shape describes a single node in the tree.
+ * The node has 'sn' direct children. From left to right, for i in 0...sn-1,
+ * s[i] is 1 if the child is a leaf node, otherwise the child is a subtree and
+ * s[i] is the number of leaf nodes within that subtree. In the latter case,
+ * the subtree is described by another struct tree_shape within the enclosing
+ * array. The tree_shapes are ordered in the array in in-order.
+ */
+struct tree_shape {
+ unsigned int state;
+ int s[50];
+ int sn;
+};
+
+static int
+init_tree_shape__(struct tree_shape ts[], int n)
+{
+ if (n <= 2) {
+ return 0;
+ }
+
+ int n_tses = 1;
+ /* Skip the first composition intentionally. */
+ ts->sn = first_composition(n, &ts->state, ts->s);
+ ts->sn = next_composition(&ts->state, ts->s, ts->sn);
+ for (int i = 0; i < ts->sn; i++) {
+ n_tses += init_tree_shape__(&ts[n_tses], ts->s[i]);
+ }
+ return n_tses;
+}
+
+/* Initializes 'ts[]' as the first in the set of all of possible shapes of
+ * trees with 'n' leaves. Returns the number of "struct tree_shape"s in the
+ * first tree shape. */
+static int
+init_tree_shape(struct tree_shape ts[], int n)
+{
+ switch (n) {
+ case 1:
+ ts->sn = 1;
+ ts->s[0] = 1;
+ return 1;
+ case 2:
+ ts->sn = 2;
+ ts->s[0] = 1;
+ ts->s[1] = 1;
+ return 1;
+ default:
+ return init_tree_shape__(ts, n);
+ }
+}
+
+/* Advances 'ts', which currently has 'n_tses' elements, to the next possible
+ * tree shape with the number of leaves passed to init_tree_shape(). Returns
+ * the number of "struct tree_shape"s in the next shape, or 0 if all tree
+ * shapes have been visited. */
+static int
+next_tree_shape(struct tree_shape ts[], int n_tses)
+{
+ if (n_tses == 1 && ts->sn == 2 && ts->s[0] == 1 && ts->s[1] == 1) {
+ return 0;
+ }
+ while (n_tses > 0) {
+ struct tree_shape *p = &ts[n_tses - 1];
+ p->sn = p->sn > 1 ? next_composition(&p->state, p->s, p->sn) : 0;
+ if (p->sn) {
+ for (int i = 0; i < p->sn; i++) {
+ n_tses += init_tree_shape__(&ts[n_tses], p->s[i]);
+ }
+ break;
+ }
+ n_tses--;
+ }
+ return n_tses;
+}
+
+static void
+print_tree_shape(const struct tree_shape ts[], int n_tses)
+{
+ for (int i = 0; i < n_tses; i++) {
+ if (i) {
+ printf(", ");
+ }
+ for (int j = 0; j < ts[i].sn; j++) {
+ int k = ts[i].s[j];
+ if (k > 9) {
+ printf("(%d)", k);
+ } else {
+ printf("%d", k);
+ }
+ }
+ }
+}
+
+static void
+test_tree_shape(struct ovs_cmdl_context *ctx)
+{
+ int n = atoi(ctx->argv[1]);
+ struct tree_shape ts[50];
+ int n_tses;
+
+ for (n_tses = init_tree_shape(ts, n); n_tses;
+ n_tses = next_tree_shape(ts, n_tses)) {
+ print_tree_shape(ts, n_tses);
+ putchar('\n');
+ }
+}
+
+/* Iteration through all possible terminal expressions (e.g. EXPR_T_CMP and
+ * EXPR_T_BOOLEAN expressions).
+ *
+ * Given a tree shape, this allows the code to try all possible ways to plug in
+ * terms.
+ *
+ * Example use:
+ *
+ * struct expr terminal;
+ * const struct expr_symbol *vars = ...;
+ * int n_vars = ...;
+ * int n_bits = ...;
+ *
+ * init_terminal(&terminal, vars[0]);
+ * do {
+ * // Something with 'terminal'.
+ * } while (next_terminal(&terminal, vars, n_vars, n_bits));
+ */
+
+/* Sets 'expr' to the first possible terminal expression. 'var' should be the
+ * first variable in the ones to be tested. */
+static void
+init_terminal(struct expr *expr, const struct expr_symbol *var)
+{
+ expr->type = EXPR_T_CMP;
+ expr->cmp.symbol = var;
+ expr->cmp.relop = rightmost_1bit_idx(test_relops);
+ memset(&expr->cmp.value, 0, sizeof expr->cmp.value);
+ memset(&expr->cmp.mask, 0, sizeof expr->cmp.mask);
+ expr->cmp.value.integer = htonll(0);
+ expr->cmp.mask.integer = htonll(1);
+}
+
+/* Returns 'x' with the rightmost contiguous string of 1s changed to 0s,
+ * e.g. 01011100 => 01000000. See H. S. Warren, Jr., _Hacker's Delight_, 2nd
+ * ed., section 2-1. */
+static unsigned int
+turn_off_rightmost_1s(unsigned int x)
+{
+ return ((x & -x) + x) & x;
+}
+
+static const struct expr_symbol *
+next_var(const struct expr_symbol *symbol,
+ const struct expr_symbol *vars[], int n_vars)
+{
+ for (int i = 0; i < n_vars; i++) {
+ if (symbol == vars[i]) {
+ return i + 1 >= n_vars ? NULL : vars[i + 1];
+ }
+ }
+ OVS_NOT_REACHED();
+}
+
+static enum expr_relop
+next_relop(enum expr_relop relop)
+{
+ unsigned int remaining_relops = test_relops & ~((1u << (relop + 1)) - 1);
+ return (remaining_relops
+ ? rightmost_1bit_idx(remaining_relops)
+ : rightmost_1bit_idx(test_relops));
+}
+
+/* Advances 'expr' to the next possible terminal expression within the 'n_vars'
+ * variables of 'n_bits' bits each in 'vars[]'. */
+static bool
+next_terminal(struct expr *expr, const struct expr_symbol *vars[], int n_vars,
+ int n_bits)
+{
+ if (expr->type == EXPR_T_BOOLEAN) {
+ if (expr->boolean) {
+ return false;
+ } else {
+ expr->boolean = true;
+ return true;
+ }
+ }
+
+ unsigned int next;
+
+ next = (ntohll(expr->cmp.value.integer)
+ + (ntohll(expr->cmp.mask.integer) << n_bits));
+ for (;;) {
+ next++;
+ unsigned m = next >> n_bits;
+ unsigned v = next & ((1u << n_bits) - 1);
+ if (next >= (1u << (2 * n_bits))) {
+ enum expr_relop old_relop = expr->cmp.relop;
+ expr->cmp.relop = next_relop(old_relop);
+ if (expr->cmp.relop <= old_relop) {
+ expr->cmp.symbol = next_var(expr->cmp.symbol,vars, n_vars);
+ if (!expr->cmp.symbol) {
+ expr->type = EXPR_T_BOOLEAN;
+ expr->boolean = false;
+ return true;
+ }
+ }
+ next = 0;
+ } else if (m == 0) {
+ /* Skip: empty mask is pathological. */
+ } else if (v & ~m) {
+ /* Skip: 1-bits in value correspond to 0-bits in mask. */
+ } else if (turn_off_rightmost_1s(m)
+ && (expr->cmp.relop != EXPR_R_EQ &&
+ expr->cmp.relop != EXPR_R_NE)) {
+ /* Skip: can't have discontiguous mask for > >= < <=. */
+ } else {
+ expr->cmp.value.integer = htonll(v);
+ expr->cmp.mask.integer = htonll(m);
+ return true;
+ }
+ }
+}
+
+static struct expr *
+make_terminal(struct expr ***terminalp)
+{
+ struct expr *e = expr_create_boolean(true);
+ **terminalp = e;
+ (*terminalp)++;
+ return e;
+}
+
+static struct expr *
+build_simple_tree(enum expr_type type, int n, struct expr ***terminalp)
+{
+ if (n == 2) {
+ struct expr *e = expr_create_andor(type);
+ for (int i = 0; i < 2; i++) {
+ struct expr *sub = make_terminal(terminalp);
+ list_push_back(&e->andor, &sub->node);
+ }
+ return e;
+ } else if (n == 1) {
+ return make_terminal(terminalp);
+ } else {
+ OVS_NOT_REACHED();
+ }
+}
+
+static struct expr *
+build_tree_shape(enum expr_type type, const struct tree_shape **tsp,
+ struct expr ***terminalp)
+{
+ const struct tree_shape *ts = *tsp;
+ (*tsp)++;
+
+ struct expr *e = expr_create_andor(type);
+ enum expr_type t = type == EXPR_T_AND ? EXPR_T_OR : EXPR_T_AND;
+ for (int i = 0; i < ts->sn; i++) {
+ struct expr *sub = (ts->s[i] > 2
+ ? build_tree_shape(t, tsp, terminalp)
+ : build_simple_tree(t, ts->s[i], terminalp));
+ list_push_back(&e->andor, &sub->node);
+ }
+ return e;
+}
+
+struct test_rule {
+ struct cls_rule cr;
+};
+
+static void
+free_rule(struct test_rule *test_rule)
+{
+ cls_rule_destroy(&test_rule->cr);
+ free(test_rule);
+}
+
+static int
+test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
+ struct expr *terminals[], int n_terminals,
+ const struct expr_symbol *vars[], int n_vars,
+ int n_bits)
+{
+ int n_tested = 0;
+
+ const unsigned int var_mask = (1u << n_bits) - 1;
+ for (int i = 0; i < n_terminals; i++) {
+ init_terminal(terminals[i], vars[0]);
+ }
+
+ struct ds s = DS_EMPTY_INITIALIZER;
+ struct flow f;
+ memset(&f, 0, sizeof f);
+ for (;;) {
+ for (int i = n_terminals - 1; ; i--) {
+ if (!i) {
+ ds_destroy(&s);
+ return n_tested;
+ }
+ if (next_terminal(terminals[i], vars, n_vars, n_bits)) {
+ break;
+ }
+ init_terminal(terminals[i], vars[0]);
+ }
+ ovs_assert(expr_honors_invariants(expr));
+
+ n_tested++;
+
+ struct expr *modified;
+ if (operation == OP_CONVERT) {
+ ds_clear(&s);
+ expr_format(expr, &s);
+
+ char *error;
+ modified = expr_parse_string(ds_cstr(&s), symtab, &error);
+ if (error) {
+ fprintf(stderr, "%s fails to parse (%s)\n",
+ ds_cstr(&s), error);
+ exit(EXIT_FAILURE);
+ }
+ } else if (operation >= OP_SIMPLIFY) {
+ modified = expr_simplify(expr_clone(expr));
+ ovs_assert(expr_honors_invariants(modified));
+
+ if (operation >= OP_NORMALIZE) {
+ modified = expr_normalize(modified);
+ ovs_assert(expr_is_normalized(modified));
+ }
+ }
+
+ struct hmap matches;
+ struct classifier cls;
+ if (operation >= OP_FLOW) {
+ struct expr_match *m;
+ struct test_rule *test_rule;
+ uint32_t n_conjs;
+
+ n_conjs = expr_to_matches(modified, &matches);
+
+ classifier_init(&cls, NULL);
+ HMAP_FOR_EACH (m, hmap_node, &matches) {
+ test_rule = xmalloc(sizeof *test_rule);
+ cls_rule_init(&test_rule->cr, &m->match, 0);
+ classifier_insert(&cls, &test_rule->cr, m->conjunctions, m->n);
+ }
+ for (uint32_t conj_id = 1; conj_id <= n_conjs; conj_id++) {
+ struct match match;
+ match_init_catchall(&match);
+ match_set_conj_id(&match, conj_id);
+
+ test_rule = xmalloc(sizeof *test_rule);
+ cls_rule_init(&test_rule->cr, &match, 0);
+ classifier_insert(&cls, &test_rule->cr, NULL, 0);
+ }
+ }
+ for (int subst = 0; subst < 1 << (n_bits * n_vars); subst++) {
+ bool expected = evaluate_expr(expr, subst, n_bits);
+ bool actual = evaluate_expr(modified, subst, n_bits);
+ if (actual != expected) {
+ struct ds expr_s, modified_s;
+
+ ds_init(&expr_s);
+ expr_format(expr, &expr_s);
+
+ ds_init(&modified_s);
+ expr_format(modified, &modified_s);
+
+ fprintf(stderr,
+ "%s evaluates to %d, but %s evaluates to %d, for",
+ ds_cstr(&expr_s), expected,
+ ds_cstr(&modified_s), actual);
+ for (int i = 0; i < n_vars; i++) {
+ if (i > 0) {
+ fputs(",", stderr);
+ }
+ fprintf(stderr, " %c = 0x%x", 'a' + i,
+ (subst >> (n_bits * i)) & var_mask);
+ }
+ putc('\n', stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (operation >= OP_FLOW) {
+ for (int i = 0; i < n_vars; i++) {
+ f.regs[i] = (subst >> (i * n_bits)) & var_mask;
+ }
+ bool found = classifier_lookup(&cls, &f, NULL) != NULL;
+ if (expected != found) {
+ struct ds expr_s, modified_s;
+
+ ds_init(&expr_s);
+ expr_format(expr, &expr_s);
+
+ ds_init(&modified_s);
+ expr_format(modified, &modified_s);
+
+ fprintf(stderr,
+ "%s and %s evaluate to %d, for",
+ ds_cstr(&expr_s), ds_cstr(&modified_s), expected);
+ for (int i = 0; i < n_vars; i++) {
+ if (i > 0) {
+ fputs(",", stderr);
+ }
+ fprintf(stderr, " %c = 0x%x", 'a' + i,
+ (subst >> (n_bits * i)) & var_mask);
+ }
+ fputs(".\n", stderr);
+
+ fprintf(stderr, "Converted to classifier:\n");
+
+ struct expr_match *m;
+ HMAP_FOR_EACH (m, hmap_node, &matches) {
+ char *s = match_to_string(&m->match,
+ OFP_DEFAULT_PRIORITY);
+ fputs(s, stderr);
+ if (m->n) {
+ for (int i = 0; i < m->n; i++) {
+ const struct cls_conjunction *c
+ = &m->conjunctions[i];
+ fprintf(stderr,
+ "%c conjunction(%"PRIu32", %d/%d)",
+ i == 0 ? ':' : ',',
+ c->id, c->clause, c->n_clauses);
+ }
+ }
+ putc('\n', stderr);
+ }
+
+ fprintf(stderr,
+ "However, %s flow was found in the classifier.\n",
+ found ? "a" : "no");
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ if (operation >= OP_FLOW) {
+ struct expr_match *m, *n;
+ struct test_rule *test_rule;
+
+ CLS_FOR_EACH (test_rule, cr, &cls) {
+ classifier_remove(&cls, &test_rule->cr);
+ ovsrcu_postpone(free_rule, test_rule);
+ }
+ classifier_destroy(&cls);
+ ovsrcu_quiesce();
+
+ HMAP_FOR_EACH_SAFE (m, n, hmap_node, &matches) {
+ hmap_remove(&matches, &m->hmap_node);
+ free(m->conjunctions);
+ free(m);
+ }
+ hmap_destroy(&matches);
+ }
+ expr_destroy(modified);
+ }
+}
+
+#ifndef _WIN32
+static void
+wait_pid(pid_t *pids, int *n)
+{
+ int status;
+ pid_t pid;
+
+ pid = waitpid(WAIT_ANY, &status, 0);
+ if (pid < 0) {
+ ovs_fatal(errno, "waitpid failed");
+ } else if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status)) {
+ exit(WEXITSTATUS(status));
+ }
+ } else if (WIFSIGNALED(status)) {
+ raise(WTERMSIG(status));
+ exit(1);
+ } else {
+ OVS_NOT_REACHED();
+ }
+
+ for (int i = 0; i < *n; i++) {
+ if (pids[i] == pid) {
+ pids[i] = pids[--*n];
+ return;
+ }
+ }
+ ovs_fatal(0, "waitpid returned unknown child");
+}
+#endif
+
+static void
+test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+ int n_terminals = atoi(ctx->argv[1]);
+ struct tree_shape ts[50];
+ int n_tses;
+
+ struct shash symtab;
+ const struct expr_symbol *vars[4];
+
+ ovs_assert(test_vars <= ARRAY_SIZE(vars));
+
+ shash_init(&symtab);
+ for (int i = 0; i < test_vars; i++) {
+ char name[2] = { 'a' + i, '\0' };
+
+ vars[i] = expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL,
+ false);
+ }
+
+#ifndef _WIN32
+ pid_t *children = xmalloc(test_parallel * sizeof *children);
+ int n_children = 0;
+#endif
+
+ int n_tested = 0;
+ for (int i = 0; i < 2; i++) {
+ enum expr_type base_type = i ? EXPR_T_OR : EXPR_T_AND;
+
+ for (n_tses = init_tree_shape(ts, n_terminals); n_tses;
+ n_tses = next_tree_shape(ts, n_tses)) {
+ const struct tree_shape *tsp = ts;
+ struct expr *terminals[50];
+ struct expr **terminalp = terminals;
+ struct expr *expr = build_tree_shape(base_type, &tsp, &terminalp);
+ ovs_assert(terminalp == &terminals[n_terminals]);
+
+ if (verbosity > 0) {
+ print_tree_shape(ts, n_tses);
+ printf(": ");
+ struct ds s = DS_EMPTY_INITIALIZER;
+ expr_format(expr, &s);
+ puts(ds_cstr(&s));
+ ds_destroy(&s);
+ }
+
+#ifndef _WIN32
+ if (test_parallel > 1) {
+ pid_t pid = xfork();
+ if (!pid) {
+ test_tree_shape_exhaustively(expr, &symtab,
+ terminals, n_terminals,
+ vars, test_vars, test_bits);
+ expr_destroy(expr);
+ exit(0);
+ } else {
+ if (n_children >= test_parallel) {
+ wait_pid(children, &n_children);
+ }
+ children[n_children++] = pid;
+ }
+ } else
+#endif
+ {
+ n_tested += test_tree_shape_exhaustively(
+ expr, &symtab, terminals, n_terminals,
+ vars, test_vars, test_bits);
+ }
+ expr_destroy(expr);
+ }
+ }
+#ifndef _WIN32
+ while (n_children > 0) {
+ wait_pid(children, &n_children);
+ }
+ free(children);
+#endif
+
+ printf("Tested ");
+ switch (operation) {
+ case OP_CONVERT:
+ printf("converting");
+ break;
+ case OP_SIMPLIFY:
+ printf("simplifying");
+ break;
+ case OP_NORMALIZE:
+ printf("normalizing");
+ break;
+ case OP_FLOW:
+ printf("converting to flows");
+ break;
+ }
+ if (n_tested) {
+ printf(" %d expressions of %d terminals", n_tested, n_terminals);
+ } else {
+ printf(" all %d-terminal expressions", n_terminals);
+ }
+ printf(" with %d vars each of %d bits in terms of operators",
+ test_vars, test_bits);
+ for (unsigned int relops = test_relops; relops;
+ relops = zero_rightmost_1bit(relops)) {
+ enum expr_relop r = rightmost_1bit_idx(relops);
+ printf(" %s", expr_relop_to_string(r));
+ }
+ printf(".\n");
+
+ expr_symtab_destroy(&symtab);
+ shash_destroy(&symtab);
+}
+
+static unsigned int
+parse_relops(const char *s)
+{
+ unsigned int relops = 0;
+ struct lexer lexer;
+
+ lexer_init(&lexer, s);
+ lexer_get(&lexer);
+ do {
+ enum expr_relop relop;
+
+ if (expr_relop_from_token(lexer.token.type, &relop)) {
+ relops |= 1u << relop;
+ lexer_get(&lexer);
+ } else {
+ ovs_fatal(0, "%s: relational operator expected at `%.*s'",
+ s, (int) (lexer.input - lexer.start), lexer.start);
+ }
+ lexer_match(&lexer, LEX_T_COMMA);
+ } while (lexer.token.type != LEX_T_END);
+ lexer_destroy(&lexer);
+
+ return relops;
+}
+
+static void
+usage(void)
+{
+ printf("\
+%s: OVN test utility\n\
+usage: test-ovn %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+lex\n\
+ Lexically analyzes OVN input from stdin and print them back on stdout.\n\
+\n\
+parse-expr\n\
+annotate-expr\n\
+simplify-expr\n\
+normalize-expr\n\
+ Parses OVN expressions from stdin and print them back on stdout after\n\
+ differing degrees of analysis. Available fields are based on packet\n\
+ headers.\n\
+\n\
+evaluate-expr A B C\n\
+ Parses OVN expressions from stdin, evaluate them with assigned values,\n\
+ and print the results on stdout. Available fields are 'a', 'b', and 'c'\n\
+ of 3 bits each. A, B, and C should be in the range 0 to 7.\n\
+\n\
+composition N\n\
+ Prints all the compositions of N on stdout.\n\
+\n\
+tree-shape N\n\
+ Prints all the tree shapes with N terminals on stdout.\n\
+\n\
+exhaustive N\n\
+ Tests that all possible Boolean expressions with N terminals are properly\n\
+ simplified, normalized, and converted to flows. Available options:\n\
+ --relops=OPERATORS Test only the specified Boolean operators.\n\
+ OPERATORS may include == != < <= > >=, space or\n\
+ comma separated. Default is all operators.\n\
+ --vars=N Number of variables to test, in range 1...4, default 2.\n\
+ --bits=N Number of bits per variable, in range 1...3, default 3.\n\
+ --operation=OPERATION Operation to test, one of: convert, simplify,\n\
+ normalize, flow. Default: flow. 'normalize' includes 'simplify',\n\
+ 'flow' includes 'simplify' and 'normaize'.\n\
+ --parallel=N Number of processes to use in parallel, default 1.\n\
+",
+ program_name, program_name);
+ exit(EXIT_SUCCESS);
+}
+
static void
test_ovn_main(int argc, char *argv[])
{
set_program_name(argv[0]);
+ test_relops = parse_relops("== != < <= > >=");
+ for (;;) {
+ enum {
+ OPT_RELOPS = UCHAR_MAX + 1,
+ OPT_VARS,
+ OPT_BITS,
+ OPT_OPERATION,
+ OPT_PARALLEL
+ };
+
+ static const struct option options[] = {
+ {"relops", required_argument, NULL, OPT_RELOPS},
+ {"vars", required_argument, NULL, OPT_VARS},
+ {"bits", required_argument, NULL, OPT_BITS},
+ {"operation", required_argument, NULL, OPT_OPERATION},
+ {"parallel", required_argument, NULL, OPT_PARALLEL},
+ {"more", no_argument, NULL, 'm'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0},
+ };
+ int option_index = 0;
+ int c = getopt_long (argc, argv, "", options, &option_index);
+
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case OPT_RELOPS:
+ test_relops = parse_relops(optarg);
+ break;
+
+ case OPT_VARS:
+ test_vars = atoi(optarg);
+ if (test_vars < 1 || test_vars > 4) {
+ ovs_fatal(0, "number of variables must be between 1 and 4");
+ }
+ break;
+
+ case OPT_BITS:
+ test_bits = atoi(optarg);
+ if (test_bits < 1 || test_bits > 3) {
+ ovs_fatal(0, "number of bits must be between 1 and 3");
+ }
+ break;
+
+ case OPT_OPERATION:
+ if (!strcmp(optarg, "convert")) {
+ operation = OP_CONVERT;
+ } else if (!strcmp(optarg, "simplify")) {
+ operation = OP_SIMPLIFY;
+ } else if (!strcmp(optarg, "normalize")) {
+ operation = OP_NORMALIZE;
+ } else if (!strcmp(optarg, "flow")) {
+ operation = OP_FLOW;
+ } else {
+ ovs_fatal(0, "%s: unknown operation", optarg);
+ }
+ break;
+
+ case OPT_PARALLEL:
+ test_parallel = atoi(optarg);
+ break;
+
+ case 'm':
+ verbosity++;
+ break;
+
+ case 'h':
+ usage();
+
+ case '?':
+ exit(1);
+
+ default:
+ abort();
+ }
+ }
+
static const struct ovs_cmdl_command commands[] = {
{"lex", NULL, 0, 0, test_lex},
+ {"parse-expr", NULL, 0, 0, test_parse_expr},
+ {"annotate-expr", NULL, 0, 0, test_annotate_expr},
+ {"simplify-expr", NULL, 0, 0, test_simplify_expr},
+ {"normalize-expr", NULL, 0, 0, test_normalize_expr},
+ {"evaluate-expr", NULL, 1, 1, test_evaluate_expr},
+ {"composition", NULL, 1, 1, test_composition},
+ {"tree-shape", NULL, 1, 1, test_tree_shape},
+ {"exhaustive", NULL, 1, 1, test_exhaustive},
{NULL, NULL, 0, 0, NULL},
};
struct ovs_cmdl_context ctx;
--
2.20.1