Exemple #1
0
size_t
ipset_node_reachable_count(const struct ipset_node_cache *cache,
                           ipset_node_id node)
{
    /* Create a set to track when we've visited a given node. */
    struct cork_hash_table  *visited = cork_pointer_hash_table_new(0, 0);

    /* And a queue of nodes to check. */
    cork_array(ipset_node_id)  queue;
    cork_array_init(&queue);

    if (ipset_node_get_type(node) == IPSET_NONTERMINAL_NODE) {
        DEBUG("Adding node %u to queue", node);
        cork_array_append(&queue, node);
    }

    /* And somewhere to store the result. */
    size_t  node_count = 0;

    /* Check each node in turn. */
    while (!cork_array_is_empty(&queue)) {
        ipset_node_id  curr = cork_array_at(&queue, --queue.size);

        /* We don't have to do anything if this node is already in the
         * visited set. */
        if (cork_hash_table_get(visited, (void *) (uintptr_t) curr) == NULL) {
            DEBUG("Visiting node %u for the first time", curr);

            /* Add the node to the visited set. */
            cork_hash_table_put
                (visited, (void *) (uintptr_t) curr,
                 (void *) (uintptr_t) true, NULL, NULL, NULL);

            /* Increase the node count. */
            node_count++;

            /* And add the node's nonterminal children to the visit
             * queue. */
            struct ipset_node  *node =
                ipset_node_cache_get_nonterminal(cache, curr);

            if (ipset_node_get_type(node->low) == IPSET_NONTERMINAL_NODE) {
                DEBUG("Adding node %u to queue", node->low);
                cork_array_append(&queue, node->low);
            }

            if (ipset_node_get_type(node->high) == IPSET_NONTERMINAL_NODE) {
                DEBUG("Adding node %u to queue", node->high);
                cork_array_append(&queue, node->high);
            }
        }
    }

    /* Return the result, freeing everything before we go. */
    cork_hash_table_free(visited);
    cork_array_done(&queue);
    return node_count;
}
Exemple #2
0
static int
write_header_v1(struct save_data *save_data,
                struct ipset_node_cache *cache, ipset_node_id root)
{
    /* Output the magic number for an IP set, and the file format
     * version that we're going to write. */
    rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true));
    rii_check(write_string(save_data->stream, MAGIC_NUMBER));
    rii_check(write_uint16(save_data->stream, 0x0001));

    /* Determine how many reachable nodes there are, to calculate the
     * size of the set. */
    size_t  nonterminal_count = ipset_node_reachable_count(cache, root);
    size_t  set_size =
        MAGIC_NUMBER_LENGTH +    /* magic number */
        sizeof(uint16_t) +        /* version number  */
        sizeof(uint64_t) +        /* length of set */
        sizeof(uint32_t) +        /* number of nonterminals */
        (nonterminal_count *     /* for each nonterminal: */
         (sizeof(uint8_t) +       /*   variable number */
          sizeof(uint32_t) +      /*   low pointer */
          sizeof(uint32_t)        /*   high pointer */
         ));

    /* If the root is a terminal, we need to add 4 bytes to the set
     * size, for storing the terminal value. */
    if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) {
        set_size += sizeof(uint32_t);
    }

    rii_check(write_uint64(save_data->stream, set_size));
    rii_check(write_uint32(save_data->stream, nonterminal_count));
    return 0;
}
Exemple #3
0
static int
save_visit_node(struct save_data *save_data,
                ipset_node_id node_id, serialized_id *dest)
{
    /* Check whether we've already serialized this node. */

    struct cork_hash_table_entry  *entry;
    bool  is_new;
    entry = cork_hash_table_get_or_create
        (save_data->serialized_ids, (void *) (uintptr_t) node_id, &is_new);

    if (!is_new) {
        *dest = (intptr_t) entry->value;
        return 0;
    } else {
        if (ipset_node_get_type(node_id) == IPSET_TERMINAL_NODE) {
            /* For terminals, there isn't really anything to do — we
             * just output the terminal node and use its value as the
             * serialized ID. */

            ipset_value  value = ipset_terminal_value(node_id);

            DEBUG("Writing terminal(%d)", value);
            rii_check(save_data->write_terminal(save_data, value));
            entry->value = (void *) (intptr_t) value;
            *dest = value;
            return 0;
        } else {
            /* For nonterminals, we drill down into the node's children
             * first, then output the nonterminal node. */

            struct ipset_node  *node =
                ipset_node_cache_get_nonterminal(save_data->cache, node_id);
            DEBUG("Visiting node %u nonterminal(x%u? %u: %u)",
                  node_id, node->variable, node->high, node->low);

            /* Output the node's nonterminal children before we output
             * the node itself. */
            serialized_id  serialized_low;
            serialized_id  serialized_high;
            rii_check(save_visit_node(save_data, node->low, &serialized_low));
            rii_check(save_visit_node(save_data, node->high, &serialized_high));

            /* Output the nonterminal */
            serialized_id  result = save_data->next_serialized_id--;
            DEBUG("Writing node %u as serialized node %d = (x%u? %d: %d)",
                  node_id, result,
                  node->variable, serialized_low, serialized_high);

            entry->value = (void *) (intptr_t) result;
            *dest = result;
            return save_data->write_nonterminal
                (save_data, result, node->variable,
                 serialized_low, serialized_high);
        }
    }
}
Exemple #4
0
static int
write_footer_v1(struct save_data *save_data,
                struct ipset_node_cache *cache, ipset_node_id root)
{
    /* If the root is a terminal node, then we output the terminal value
     * in place of the (nonexistent) list of nonterminal nodes. */

    if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) {
        ipset_value  value = ipset_terminal_value(root);
        return write_uint32(save_data->stream, value);
    }

    return 0;
}
Exemple #5
0
/**
 * Add the given node ID to the node stack, and trace down from it
 * until we find a terminal node.  Assign values to the variables for
 * each nonterminal that encounter along the way.  We check low edges
 * first, so each new variable we encounter will be assigned FALSE.
 * (The high edges will be checked eventually by a call to the
 * ipset_bdd_iterator_advance() function.)
 */
static void
add_node(struct ipset_bdd_iterator *iterator, ipset_node_id node_id)
{
    /* Keep tracing down low edges until we reach a terminal. */
    while (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) {
        /* Add this nonterminal node to the stack, and trace down
         * further into the tree.  We check low edges first, so set the
         * node's variable to FALSE in the assignment. */
        struct ipset_node  *node =
            ipset_node_cache_get_nonterminal(iterator->cache, node_id);

        cork_array_append(&iterator->stack, node_id);
        ipset_assignment_set(iterator->assignment, node->variable, false);

        node_id = node->low;
    }

    /* Once we find a terminal node, save it away in the iterator result
     * and return. */
    iterator->value = ipset_terminal_value(node_id);
}
static ipset_node_id_t
apply_ite(ipset_node_cache_t *cache,
          ipset_node_id_t f,
          ipset_node_id_t g,
          ipset_node_id_t h)
{
    /*
     * We know this isn't a trivial case, since otherwise it wouldn't
     * been picked up in cached_ite(), so we need to recurse.
     */

    g_assert(ipset_node_get_type(f) == IPSET_NONTERMINAL_NODE);

    ipset_node_t  *f_node = ipset_nonterminal_node(f);
    ipset_node_t  *g_node = NULL;
    ipset_node_t  *h_node = NULL;

    /*
     * There's at least one nonterminal node.  We need the lowest
     * nonterminal variable index.
     */

    ipset_variable_t  min_variable = f_node->variable;

    if (ipset_node_get_type(g) == IPSET_NONTERMINAL_NODE)
    {
        g_node = ipset_nonterminal_node(g);

        if (g_node->variable < min_variable)
        {
            min_variable = g_node->variable;
        }
    }

    if (ipset_node_get_type(h) == IPSET_NONTERMINAL_NODE)
    {
        h_node = ipset_nonterminal_node(h);

        if (h_node->variable < min_variable)
        {
            min_variable = h_node->variable;
        }
    }

    /*
     * 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.
     */

    ipset_node_id_t  low_f, high_f;
    ipset_node_id_t  low_g, high_g;
    ipset_node_id_t  low_h, high_h;

    /* we know that F is nonterminal */
    if (f_node->variable == min_variable)
    {
        low_f = f_node->low;
        high_f = f_node->high;
    } else {
        low_f = f;
        high_f = f;
    }

    if ((ipset_node_get_type(g) == IPSET_NONTERMINAL_NODE) &&
        (g_node->variable == min_variable))
    {
        low_g = g_node->low;
        high_g = g_node->high;
    } else {
        low_g = g;
        high_g = g;
    }

    if ((ipset_node_get_type(h) == IPSET_NONTERMINAL_NODE) &&
        (h_node->variable == min_variable))
    {
        low_h = h_node->low;
        high_h = h_node->high;
    } else {
        low_h = h;
        high_h = h;
    }

    /*
     * Perform the recursion.
     */

    ipset_node_id_t low_result =
        cached_ite(cache, low_f, low_g, low_h);
    ipset_node_id_t high_result =
        cached_ite(cache, high_f, high_g, high_h);

    return ipset_node_cache_nonterminal
        (cache, min_variable, low_result, high_result);
}
static ipset_node_id_t
cached_ite(ipset_node_cache_t *cache,
           ipset_node_id_t f,
           ipset_node_id_t g,
           ipset_node_id_t h)
{
    g_d_debug("Applying ITE(%p,%p,%p)", f, g, h);

    /*
     * Some trivial cases first.
     */

    /*
     * If F is a terminal, then we're in one of the following two
     * cases:
     *
     *   ITE(1,G,H) = G
     *   ITE(0,G,H) = H
     */

    if (ipset_node_get_type(f) == IPSET_TERMINAL_NODE)
    {
        ipset_range_t  f_value = ipset_terminal_value(f);
        ipset_node_id_t  result = (f_value == 0)? h: g;
        g_d_debug("Trivial result = %p", result);
        return result;
    }

    /*
     * ITE(F,G,G) == G
     */

    if (g == h)
    {
        g_d_debug("Trivial result = %p", g);
        return g;
    }

    /*
     * ITE(F,1,0) = F
     */

    if ((ipset_node_get_type(g) == IPSET_TERMINAL_NODE) &&
        (ipset_node_get_type(h) == IPSET_TERMINAL_NODE))
    {
        ipset_range_t  g_value = ipset_terminal_value(g);
        ipset_range_t  h_value = ipset_terminal_value(h);

        if ((g_value == 1) && (h_value == 0))
        {
            g_d_debug("Trivial result = %p", f);
            return f;
        }
    }

    /*
     * Check to see if we've already performed the operation on these
     * operands.
     */

    ipset_trinary_key_t  search_key;
    ipset_trinary_key_init(&search_key, f, g, h);

    gpointer  found_key;
    gpointer  found_result;
    gboolean  node_exists =
        g_hash_table_lookup_extended(cache->ite_cache,
                                     &search_key,
                                     &found_key,
                                     &found_result);

    if (node_exists)
    {
        /*
         * There's a result in the cache, so return it.
         */

        g_d_debug("Existing result = %p", found_result);
        return found_result;
    } else {
        /*
         * This result doesn't exist yet.  Allocate a permanent copy
         * of the key.  Apply the operator, add the result to the
         * cache, and then return it.
         */

        ipset_trinary_key_t  *real_key =
            g_slice_new(ipset_trinary_key_t);
        memcpy(real_key, &search_key, sizeof(ipset_trinary_key_t));

        ipset_node_id_t  result =
            apply_ite(cache, f, g, h);
        g_d_debug("NEW result = %p", result);

        g_hash_table_insert(cache->ite_cache, real_key, result);
        return result;
    }
}