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; }
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; }
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); } } }
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; }
/** * 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; } }