|
|
/* -*- coding: utf-8 -*-
* ---------------------------------------------------------------------- * Copyright © 2010-2013, RedJack, LLC. * All rights reserved. * * Please see the LICENSE.txt file in this distribution for license * details. * ---------------------------------------------------------------------- */
#include <stdio.h>
#include <string.h>
#include <libcork/core.h>
#include "ipset/bdd/nodes.h"
#include "ipset/bits.h"
#include "ipset/logging.h"
void ipset_node_fprint(FILE *stream, struct ipset_node *node) { fprintf(stream, "nonterminal(x%u? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")", node->variable, IPSET_NODE_ID_VALUES(node->high), IPSET_NODE_ID_VALUES(node->low)); }
static cork_hash ipset_node_hash(void *user_data, const void *key) { const struct ipset_node *node = key; /* Hash of "ipset_node" */ cork_hash hash = 0xf3b7dc44; hash = cork_hash_variable(hash, node->variable); hash = cork_hash_variable(hash, node->low); hash = cork_hash_variable(hash, node->high); return hash; }
static bool ipset_node_equals(void *user_data, const void *key1, const void *key2) { const struct ipset_node *node1 = key1; const struct ipset_node *node2 = key2;
if (node1 == node2) { return true; }
return (node1->variable == node2->variable) && (node1->low == node2->low) && (node1->high == node2->high); }
/* The free list in an ipset_node_cache is represented by a
* singly-linked list of indices into the chunk array. Since the * ipset_node instance is unused for nodes in the free list, we reuse * the refcount field to store the "next" index. */
#define IPSET_NULL_INDEX ((ipset_variable) -1)
struct ipset_node_cache * ipset_node_cache_new() { struct ipset_node_cache *cache = cork_new(struct ipset_node_cache); cork_array_init(&cache->chunks); cache->largest_index = 0; cache->free_list = IPSET_NULL_INDEX; cache->node_cache = cork_hash_table_new(0, 0); cork_hash_table_set_hash (cache->node_cache, (cork_hash_f) ipset_node_hash); cork_hash_table_set_equals (cache->node_cache, (cork_equals_f) ipset_node_equals); return cache; }
void ipset_node_cache_free(struct ipset_node_cache *cache) { size_t i; for (i = 0; i < cork_array_size(&cache->chunks); i++) { free(cork_array_at(&cache->chunks, i)); } cork_array_done(&cache->chunks); cork_hash_table_free(cache->node_cache); free(cache); }
/**
* Returns the index of a new ipset_node instance. */ static ipset_value ipset_node_cache_alloc_node(struct ipset_node_cache *cache) { if (cache->free_list == IPSET_NULL_INDEX) { /* Nothing in the free list; need to allocate a new node. */ ipset_value next_index = cache->largest_index++; ipset_value chunk_index = next_index >> IPSET_BDD_NODE_CACHE_BIT_SIZE; if (chunk_index >= cork_array_size(&cache->chunks)) { /* We've filled up all of the existing chunks, and need to
* create a new one. */ DEBUG(" (allocating chunk %zu)", cork_array_size(&cache->chunks)); struct ipset_node *new_chunk = cork_calloc (IPSET_BDD_NODE_CACHE_SIZE, sizeof(struct ipset_node)); cork_array_append(&cache->chunks, new_chunk); } return next_index; } else { /* Reuse a recently freed node. */ ipset_value next_index = cache->free_list; struct ipset_node *node = ipset_node_cache_get_nonterminal_by_index(cache, next_index); cache->free_list = node->refcount; return next_index; } }
ipset_node_id ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node_id) { if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) { struct ipset_node *node = ipset_node_cache_get_nonterminal(cache, node_id); DEBUG(" [incref " IPSET_NODE_ID_FORMAT "]", IPSET_NODE_ID_VALUES(node_id)); node->refcount++; } return node_id; }
void ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node_id) { if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) { struct ipset_node *node = ipset_node_cache_get_nonterminal(cache, node_id); DEBUG(" [decref " IPSET_NODE_ID_FORMAT "]", IPSET_NODE_ID_VALUES(node_id)); if (--node->refcount == 0) { DEBUG(" [free " IPSET_NODE_ID_FORMAT "]", IPSET_NODE_ID_VALUES(node_id)); ipset_node_decref(cache, node->low); ipset_node_decref(cache, node->high); cork_hash_table_delete(cache->node_cache, node, NULL, NULL);
/* Add the node to the free list */ node->refcount = cache->free_list; cache->free_list = ipset_nonterminal_value(node_id); } } }
bool ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1, ipset_node_id node_id1, const struct ipset_node_cache *cache2, ipset_node_id node_id2) { struct ipset_node *node1; struct ipset_node *node2;
if (ipset_node_get_type(node_id1) != ipset_node_get_type(node_id2)) { return false; }
if (ipset_node_get_type(node_id1) == IPSET_TERMINAL_NODE) { return node_id1 == node_id2; }
node1 = ipset_node_cache_get_nonterminal(cache1, node_id1); node2 = ipset_node_cache_get_nonterminal(cache2, node_id2); return (node1->variable == node2->variable) && ipset_node_cache_nodes_equal(cache1, node1->low, cache2, node2->low) && ipset_node_cache_nodes_equal(cache1, node1->high, cache2, node2->high); }
ipset_node_id ipset_node_cache_nonterminal(struct ipset_node_cache *cache, ipset_variable variable, ipset_node_id low, ipset_node_id high) { /* Don't allow any nonterminals whose low and high subtrees are the
* same, since the nonterminal would be redundant. */ if (CORK_UNLIKELY(low == high)) { DEBUG(" [ SKIP nonterminal(x%u? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]", variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low)); ipset_node_decref(cache, high); return low; }
/* Check to see if there's already a nonterminal with these contents
* in the cache. */ DEBUG(" [search nonterminal(x%u? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]", variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low));
struct ipset_node search_node; search_node.variable = variable; search_node.low = low; search_node.high = high;
bool is_new; struct cork_hash_table_entry *entry = cork_hash_table_get_or_create (cache->node_cache, &search_node, &is_new);
if (!is_new) { /* There's already a node with these contents, so return its ID. */ ipset_node_id node_id = (uintptr_t) entry->value; DEBUG(" [reuse " IPSET_NODE_ID_FORMAT "]", IPSET_NODE_ID_VALUES(node_id)); ipset_node_incref(cache, node_id); ipset_node_decref(cache, low); ipset_node_decref(cache, high); return node_id; } else { /* This node doesn't exist yet. Allocate a permanent copy of
* the node, add it to the cache, and then return its ID. */ ipset_value new_index = ipset_node_cache_alloc_node(cache); ipset_node_id new_node_id = ipset_nonterminal_node_id(new_index); struct ipset_node *real_node = ipset_node_cache_get_nonterminal_by_index(cache, new_index); real_node->refcount = 1; real_node->variable = variable; real_node->low = low; real_node->high = high; entry->key = real_node; entry->value = (void *) (uintptr_t) new_node_id; DEBUG(" [new " IPSET_NODE_ID_FORMAT "]", IPSET_NODE_ID_VALUES(new_node_id)); return new_node_id; } }
bool ipset_bool_array_assignment(const void *user_data, ipset_variable variable) { const bool *bool_array = (const bool *) user_data; return bool_array[variable]; }
bool ipset_bit_array_assignment(const void *user_data, ipset_variable variable) { return IPSET_BIT_GET(user_data, variable); }
ipset_value ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node_id, ipset_assignment_func assignment, const void *user_data) { ipset_node_id curr_node_id = node_id; DEBUG("Evaluating BDD node " IPSET_NODE_ID_FORMAT, IPSET_NODE_ID_VALUES(node_id));
/* As long as the current node is a nonterminal, we have to check
* the value of the current variable. */ while (ipset_node_get_type(curr_node_id) == IPSET_NONTERMINAL_NODE) { /* We have to look up this variable in the assignment. */ struct ipset_node *node = ipset_node_cache_get_nonterminal(cache, curr_node_id); bool this_value = assignment(user_data, node->variable); DEBUG("[%3u] Nonterminal " IPSET_NODE_ID_FORMAT, node->variable, IPSET_NODE_ID_VALUES(curr_node_id)); DEBUG("[%3u] x%u = %s", node->variable, node->variable, this_value? "TRUE": "FALSE");
if (this_value) { /* This node's variable is true in the assignment vector, so
* trace down the high subtree. */ curr_node_id = node->high; } else { /* This node's variable is false in the assignment vector,
* so trace down the low subtree. */ curr_node_id = node->low; } }
/* Once we find a terminal node, we've got the final result. */ DEBUG("Evaluated result is %u", ipset_terminal_value(curr_node_id)); return ipset_terminal_value(curr_node_id); }
/* A “fake” BDD node given by an assignment. */ struct ipset_fake_node { ipset_variable current_var; ipset_variable var_count; ipset_assignment_func assignment; const void *user_data; ipset_value value; };
/* A fake BDD node representing the terminal 0 value. */ static struct ipset_fake_node fake_terminal_0 = { 0, 0, NULL, 0, 0 };
/* We set elements in a map using the if-then-else (ITE) operator:
* * new_set = new_element? new_value: old_set * * The below is a straight copy of the standard trinary APPLY from the BDD * literature, but without the caching of the results. And also with the * wrinkle that the F argument to ITE (i.e., new_element) is given by an * assignment, and not by a BDD node. (This lets us skip constructing the BDD * for the assignment, saving us a few cycles.) */
static ipset_node_id ipset_apply_ite(struct ipset_node_cache *cache, struct ipset_fake_node *f, ipset_value g, ipset_node_id h) { ipset_node_id h_low; ipset_node_id h_high; ipset_node_id result_low; ipset_node_id result_high;
/* If F is a terminal, then we're in one of the following two
* cases: * * 1? G: H == G * 0? G: H == H */ if (f->current_var == f->var_count) { ipset_node_id result; DEBUG("[%3u] F is terminal (value %u)", f->current_var, f->value);
if (f->value == 0) { DEBUG("[%3u] 0? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT " = " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(ipset_terminal_node_id(g)), IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h)); result = ipset_node_incref(cache, h); } else { result = ipset_terminal_node_id(g); DEBUG("[%3u] 1? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT " = " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(result), IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(result)); }
return result; }
/* F? G: G == G */ if (h == ipset_terminal_node_id(g)) { DEBUG("[%3u] F? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT " = " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h)); return h; }
/* From here to the end of the function, we know that F is a
* nonterminal. */ DEBUG("[%3u] F is nonterminal", f->current_var);
/* We're going to do two recursive calls, a “low” one and a “high” one. For
* each nonterminal that has the minimum variable number, we use its low and * high pointers in the respective recursive call. For all other * nonterminals, and for all terminals, we use the operand itself. */
if (ipset_node_get_type(h) == IPSET_NONTERMINAL_NODE) { struct ipset_node *h_node = ipset_node_cache_get_nonterminal(cache, h);
DEBUG("[%3u] H is nonterminal (variable %u)", f->current_var, h_node->variable);
if (h_node->variable < f->current_var) { /* var(F) > var(H), so we only recurse down the H branches. */ DEBUG("[%3u] Recursing only down H", f->current_var); DEBUG("[%3u] Recursing high", f->current_var); result_high = ipset_apply_ite(cache, f, g, h_node->high); DEBUG("[%3u] Back from high recursion", f->current_var); DEBUG("[%3u] Recursing low", f->current_var); result_low = ipset_apply_ite(cache, f, g, h_node->low); DEBUG("[%3u] Back from low recursion", f->current_var); return ipset_node_cache_nonterminal (cache, h_node->variable, result_low, result_high); } else if (h_node->variable == f->current_var) { /* var(F) == var(H), so we recurse down both branches. */ DEBUG("[%3u] Recursing down both F and H", f->current_var); h_low = h_node->low; h_high = h_node->high; } else { /* var(F) < var(H), so we only recurse down the F branches. */ DEBUG("[%3u] Recursing only down F", f->current_var); h_low = h; h_high = h; } } else { /* H in nonterminal, so we only recurse down the F branches. */ DEBUG("[%3u] H is terminal (value %u)", f->current_var, ipset_terminal_value(h)); DEBUG("[%3u] Recursing only down F", f->current_var); h_low = h; h_high = h; }
/* F is a “fake” nonterminal node, since it comes from our assignment. One
* of its branches will be the 0 terminal, and the other will be the fake * nonterminal for the next variable in the assignment. (Which one is low * and which one is high depends on the value of the current variable in the * assignment.) */
if (f->assignment(f->user_data, f->current_var)) { /* The current variable is set in F. The low branch is terminal 0; the
* high branch is the next variable in F. */ DEBUG("[%3u] x[%u] is set", f->current_var, f->current_var); DEBUG("[%3u] Recursing high", f->current_var); f->current_var++; result_high = ipset_apply_ite(cache, f, g, h_high); f->current_var--; DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(result_high)); DEBUG("[%3u] Recursing low", f->current_var); fake_terminal_0.current_var = f->var_count; fake_terminal_0.var_count = f->var_count; result_low = ipset_apply_ite(cache, &fake_terminal_0, g, h_low); DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(result_low)); } else { /* The current variable is NOT set in F. The high branch is terminal 0;
* the low branch is the next variable in F. */ DEBUG("[%3u] x[%u] is NOT set", f->current_var, f->current_var); DEBUG("[%3u] Recursing high", f->current_var); fake_terminal_0.current_var = f->var_count; fake_terminal_0.var_count = f->var_count; result_high = ipset_apply_ite(cache, &fake_terminal_0, g, h_high); DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(result_high)); DEBUG("[%3u] Recursing low", f->current_var); f->current_var++; result_low = ipset_apply_ite(cache, f, g, h_low); f->current_var--; DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT, f->current_var, IPSET_NODE_ID_VALUES(result_low)); }
return ipset_node_cache_nonterminal (cache, f->current_var, result_low, result_high); }
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 var_count, ipset_value value) { struct ipset_fake_node f = { 0, var_count, assignment, user_data, 1 }; DEBUG("Inserting new element"); return ipset_apply_ite(cache, &f, value, node); }
|