/* -*- coding: utf-8 -*- * ---------------------------------------------------------------------- * Copyright © 2010-2013, RedJack, LLC. * All rights reserved. * * Please see the LICENSE.txt file in this distribution for license * details. * ---------------------------------------------------------------------- */ #ifndef IPSET_BDD_NODES_H #define IPSET_BDD_NODES_H #include #include #include /*----------------------------------------------------------------------- * Preliminaries */ /** * Each variable in a BDD is referred to by number. */ typedef unsigned int ipset_variable; /** * Each BDD terminal represents an integer value. The integer must be * non-negative, but must be within the range of the signed * integer type. */ typedef unsigned int ipset_value; /** * An identifier for each distinct node in a BDD. * * Internal implementation note. Since pointers are aligned to at * least two bytes, the ID of a terminal node has its LSB set to 1, * and has the terminal value stored in the remaining bits. The ID of * a nonterminal node is simply a pointer to the node struct. */ typedef unsigned int ipset_node_id; /** * Nodes can either be terminal or nonterminal. */ enum ipset_node_type { IPSET_NONTERMINAL_NODE = 0, IPSET_TERMINAL_NODE = 1 }; /** * Return the type of node represented by a particular node ID. */ #define ipset_node_get_type(node_id) ((node_id) & 0x01) #define IPSET_NODE_ID_FORMAT "%s%u" #define IPSET_NODE_ID_VALUES(node_id) \ (ipset_node_get_type((node_id)) == IPSET_NONTERMINAL_NODE? "s": ""), \ ((node_id) >> 1) /*----------------------------------------------------------------------- * Terminal nodes */ /** * Return the value of a terminal node. The result is undefined if * the node ID represents a nonterminal. */ #define ipset_terminal_value(node_id) ((node_id) >> 1) /** * Creates a terminal node ID from a terminal value. */ #define ipset_terminal_node_id(value) \ (((value) << 1) | IPSET_TERMINAL_NODE) /*----------------------------------------------------------------------- * Nonterminal nodes */ /** * A nonterminal BDD node. This is an inner node of the BDD tree. * The node represents one variable in an overall variable assignment. * The node has two children: a “low” child and a “high” child. The * low child is the subtree that applies when the node's variable is * false or 0; the high child is the subtree that applies when it's * true or 1. * * This type does not take care of ensuring that all BDD nodes are * reduced; that is handled by the node_cache class. */ struct ipset_node { /** The reference count for this node. */ unsigned int refcount; /** The variable that this node represents. */ ipset_variable variable; /** The subtree node for when the variable is false. */ ipset_node_id low; /** The subtree node for when the variable is true. */ ipset_node_id high; }; /** * Return the "value" of a nonterminal node. The value of a nonterminal * is the index into the node array of the cache that the node belongs * to. */ #define ipset_nonterminal_value(node_id) ((node_id) >> 1) /** * Creates a nonterminal node ID from a nonterminal value. */ #define ipset_nonterminal_node_id(value) \ (((value) << 1) | IPSET_NONTERMINAL_NODE) /** * Print out a node object. */ void ipset_node_fprint(FILE *stream, struct ipset_node *node); /*----------------------------------------------------------------------- * Node caches */ /** * The log2 of the size of each chunk of BDD nodes. */ /* 16K elements per cache */ #define IPSET_BDD_NODE_CACHE_BIT_SIZE 6 #define IPSET_BDD_NODE_CACHE_SIZE (1 << IPSET_BDD_NODE_CACHE_BIT_SIZE) #define IPSET_BDD_NODE_CACHE_MASK (IPSET_BDD_NODE_CACHE_SIZE - 1) /** * A cache for BDD nodes. By creating and retrieving nodes through * the cache, we ensure that a BDD is reduced. */ struct ipset_node_cache { /** The storage for the nodes managed by this cache. */ cork_array(struct ipset_node *) chunks; /** The largest nonterminal index that has been handed out. */ ipset_value largest_index; /** The index of the first node in the free list. */ ipset_value free_list; /** A cache of the nonterminal nodes, keyed by their contents. */ struct cork_hash_table *node_cache; }; /** * Returns the index of the chunk that the given nonterminal lives in. */ #define ipset_nonterminal_chunk_index(index) \ ((index) >> IPSET_BDD_NODE_CACHE_BIT_SIZE) /** * Returns the offset of the given nonterminal within its chunk. */ #define ipset_nonterminal_chunk_offset(index) \ ((index) & IPSET_BDD_NODE_CACHE_MASK) /** * Returns a pointer to the ipset_node for a given nonterminal index. */ #define ipset_node_cache_get_nonterminal_by_index(cache, index) \ (&cork_array_at(&(cache)->chunks, ipset_nonterminal_chunk_index((index))) \ [ipset_nonterminal_chunk_offset((index))]) /** * Returns the ipset_node for a given nonterminal node ID. */ #define ipset_node_cache_get_nonterminal(cache, node_id) \ (ipset_node_cache_get_nonterminal_by_index \ ((cache), ipset_nonterminal_value((node_id)))) /** * Create a new node cache. */ struct ipset_node_cache * ipset_node_cache_new(void); /** * Free a node cache. */ void ipset_node_cache_free(struct ipset_node_cache *cache); /** * Create a new nonterminal node with the given contents, returning * its ID. This function ensures that there is only one node with the * given contents in this cache. * * Steals references to low and high. */ ipset_node_id ipset_node_cache_nonterminal(struct ipset_node_cache *cache, ipset_variable variable, ipset_node_id low, ipset_node_id high); /** * Increment the reference count of a nonterminal node. (This is a * no-op for terminal nodes.) */ ipset_node_id ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node); /** * Decrement the reference count of a nonterminal node. If the * reference count reaches 0, the storage for the node will be * reclaimed. (This is a no-op for terminal nodes.) */ void ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node); /** * Return the number of nodes that are reachable from the given node. * This does not include duplicates if a node is reachable via more * than one path. */ size_t ipset_node_reachable_count(const struct ipset_node_cache *cache, ipset_node_id node); /** * Return the amount of memory used by the nodes in the given BDD. */ size_t ipset_node_memory_size(const struct ipset_node_cache *cache, ipset_node_id node); /** * Load a BDD from an input stream. The error field is filled in with * an error condition is the BDD can't be read for any reason. */ ipset_node_id ipset_node_cache_load(FILE *stream, struct ipset_node_cache *cache); /** * Save a BDD to an output stream. This encodes the set using only * those nodes that are reachable from the BDD's root node. */ int ipset_node_cache_save(struct cork_stream_consumer *stream, struct ipset_node_cache *cache, ipset_node_id node); /** * Compare two BDD nodes, possibly from different caches, for equality. */ bool ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1, ipset_node_id node1, const struct ipset_node_cache *cache2, ipset_node_id node2); /** * Save a GraphViz dot graph for a BDD. The graph script is written * to the given output stream. This graph only includes those nodes * that are reachable from the BDD's root node. */ int ipset_node_cache_save_dot(struct cork_stream_consumer *stream, struct ipset_node_cache *cache, ipset_node_id node); /*----------------------------------------------------------------------- * BDD operators */ /** * A function that provides the value for each variable in a BDD. */ typedef bool (*ipset_assignment_func)(const void *user_data, ipset_variable variable); /** * An assignment function that gets the variable values from an array * of gbooleans. */ bool ipset_bool_array_assignment(const void *user_data, ipset_variable variable); /** * An assignment function that gets the variable values from an array * of bits. */ bool ipset_bit_array_assignment(const void *user_data, ipset_variable variable); /** * Evaluate a BDD given a particular assignment of variables. */ ipset_value ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node, ipset_assignment_func assignment, const void *user_data); /** * Add an assignment to the BDD. */ ipset_node_id ipset_node_insert(struct ipset_node_cache *cache, ipset_node_id node, ipset_assignment_func assignment, const void *user_data, ipset_variable variable_count, ipset_value value); /*----------------------------------------------------------------------- * Variable assignments */ /** * Each variable in the input to a Boolean function can be true or * false; it can also be EITHER, which means that the variable can be * either true or false in a particular assignment without affecting * the result of the function. */ enum ipset_tribool { IPSET_FALSE = 0, IPSET_TRUE = 1, IPSET_EITHER = 2 }; /** * An assignment is a mapping of variable numbers to Boolean values. * It represents an input to a Boolean function that maps to a * particular output value. Each variable in the input to a Boolean * function can be true or false; it can also be EITHER, which means * that the variable can be either true or false in a particular * assignment without affecting the result of the function. */ struct ipset_assignment { /** * The underlying variable assignments are stored in a vector of * tribools. Every variable that has a true or false value must * appear in the vector. Variables that are EITHER only have to * appear to prevent gaps in the vector. Any variables outside * the range of the vector are assumed to be EITHER. */ cork_array(enum ipset_tribool) values; }; /** * Create a new assignment where all variables are indeterminite. */ struct ipset_assignment * ipset_assignment_new(); /** * Free an assignment. */ void ipset_assignment_free(struct ipset_assignment *assignment); /** * Compare two assignments for equality. */ bool ipset_assignment_equal(const struct ipset_assignment *assignment1, const struct ipset_assignment *assignment2); /** * Set the given variable, and all higher variables, to the EITHER * value. */ void ipset_assignment_cut(struct ipset_assignment *assignment, ipset_variable var); /** * Clear the assignment, setting all variables to the EITHER value. */ void ipset_assignment_clear(struct ipset_assignment *assignment); /** * Return the value assigned to a particular variable. */ enum ipset_tribool ipset_assignment_get(struct ipset_assignment *assignment, ipset_variable var); /** * Set the value assigned to a particular variable. */ void ipset_assignment_set(struct ipset_assignment *assignment, ipset_variable var, enum ipset_tribool value); /*----------------------------------------------------------------------- * Expanded assignments */ /** * An iterator for expanding a variable assignment. For each EITHER * variable in the assignment, the iterator yields a result with both * values. */ struct ipset_expanded_assignment { /** Whether there are any more assignments in this iterator. */ bool finished; /** * The variable values in the current expanded assignment. Since * there won't be any EITHERs in the expanded assignment, we can * use a byte array, and represent each variable by a single bit. */ struct cork_buffer values; /** * An array containing all of the variables that are EITHER in the * original assignment. */ cork_array(ipset_variable) eithers; }; /** * Return an iterator that expands a variable assignment. For each * variable that's EITHER in the assignment, the iterator yields a * result with both values. The iterator will ensure that the * specified number of variables are given concrete values. */ struct ipset_expanded_assignment * ipset_assignment_expand(const struct ipset_assignment *assignment, ipset_variable var_count); /** * Free an expanded assignment iterator. */ void ipset_expanded_assignment_free(struct ipset_expanded_assignment *exp); /** * Advance the iterator to the next assignment. */ void ipset_expanded_assignment_advance(struct ipset_expanded_assignment *exp); /*----------------------------------------------------------------------- * BDD iterators */ /** * An iterator for walking through the assignments for a given BDD * node. * * The iterator walks through each path in the BDD tree, stopping at * each terminal node. Each time we reach a terminal node, we yield a * new ipset_assignment object representing the assignment of variables * along the current path. * * We maintain a stack of nodes leading to the current terminal, which * allows us to backtrack up the path to find the next terminal when * we increment the iterator. */ struct ipset_bdd_iterator { /** Whether there are any more assignments in this iterator. */ bool finished; /** The node cache that we're iterating through. */ struct ipset_node_cache *cache; /** * The sequence of nonterminal nodes leading to the current * terminal. */ cork_array(ipset_node_id) stack; /** The current assignment. */ struct ipset_assignment *assignment; /** * The value of the BDD's function when applied to the current * assignment. */ ipset_value value; }; /** * Return an iterator that yields all of the assignments in the given * BDD. The iterator contains two items of interest. The first is an * ipset_assignment providing the value that each variable takes, while * the second is the terminal value that is the result of the BDD's * function when applied to that variable assignment. */ struct ipset_bdd_iterator * ipset_node_iterate(struct ipset_node_cache *cache, ipset_node_id root); /** * Free a BDD iterator. */ void ipset_bdd_iterator_free(struct ipset_bdd_iterator *iterator); /** * Advance the iterator to the next assignment. */ void ipset_bdd_iterator_advance(struct ipset_bdd_iterator *iterator); #endif /* IPSET_BDD_NODES_H */