You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
17 KiB

10 years ago
  1. /* -*- coding: utf-8 -*-
  2. * ----------------------------------------------------------------------
  3. * Copyright © 2010-2013, RedJack, LLC.
  4. * All rights reserved.
  5. *
  6. * Please see the LICENSE.txt file in this distribution for license
  7. * details.
  8. * ----------------------------------------------------------------------
  9. */
  10. #include <stdio.h>
  11. #include <string.h>
  12. #include <libcork/core.h>
  13. #include "ipset/bdd/nodes.h"
  14. #include "ipset/bits.h"
  15. #include "ipset/logging.h"
  16. void
  17. ipset_node_fprint(FILE *stream, struct ipset_node *node)
  18. {
  19. fprintf(stream,
  20. "nonterminal(x%u? " IPSET_NODE_ID_FORMAT
  21. ": " IPSET_NODE_ID_FORMAT ")",
  22. node->variable,
  23. IPSET_NODE_ID_VALUES(node->high),
  24. IPSET_NODE_ID_VALUES(node->low));
  25. }
  26. static cork_hash
  27. ipset_node_hash(void *user_data, const void *key)
  28. {
  29. const struct ipset_node *node = key;
  30. /* Hash of "ipset_node" */
  31. cork_hash hash = 0xf3b7dc44;
  32. hash = cork_hash_variable(hash, node->variable);
  33. hash = cork_hash_variable(hash, node->low);
  34. hash = cork_hash_variable(hash, node->high);
  35. return hash;
  36. }
  37. static bool
  38. ipset_node_equals(void *user_data, const void *key1, const void *key2)
  39. {
  40. const struct ipset_node *node1 = key1;
  41. const struct ipset_node *node2 = key2;
  42. if (node1 == node2) {
  43. return true;
  44. }
  45. return
  46. (node1->variable == node2->variable) &&
  47. (node1->low == node2->low) &&
  48. (node1->high == node2->high);
  49. }
  50. /* The free list in an ipset_node_cache is represented by a
  51. * singly-linked list of indices into the chunk array. Since the
  52. * ipset_node instance is unused for nodes in the free list, we reuse
  53. * the refcount field to store the "next" index. */
  54. #define IPSET_NULL_INDEX ((ipset_variable) -1)
  55. struct ipset_node_cache *
  56. ipset_node_cache_new()
  57. {
  58. struct ipset_node_cache *cache = cork_new(struct ipset_node_cache);
  59. cork_array_init(&cache->chunks);
  60. cache->largest_index = 0;
  61. cache->free_list = IPSET_NULL_INDEX;
  62. cache->node_cache = cork_hash_table_new(0, 0);
  63. cork_hash_table_set_hash
  64. (cache->node_cache, (cork_hash_f) ipset_node_hash);
  65. cork_hash_table_set_equals
  66. (cache->node_cache, (cork_equals_f) ipset_node_equals);
  67. return cache;
  68. }
  69. void
  70. ipset_node_cache_free(struct ipset_node_cache *cache)
  71. {
  72. size_t i;
  73. for (i = 0; i < cork_array_size(&cache->chunks); i++) {
  74. free(cork_array_at(&cache->chunks, i));
  75. }
  76. cork_array_done(&cache->chunks);
  77. cork_hash_table_free(cache->node_cache);
  78. free(cache);
  79. }
  80. /**
  81. * Returns the index of a new ipset_node instance.
  82. */
  83. static ipset_value
  84. ipset_node_cache_alloc_node(struct ipset_node_cache *cache)
  85. {
  86. if (cache->free_list == IPSET_NULL_INDEX) {
  87. /* Nothing in the free list; need to allocate a new node. */
  88. ipset_value next_index = cache->largest_index++;
  89. ipset_value chunk_index = next_index >> IPSET_BDD_NODE_CACHE_BIT_SIZE;
  90. if (chunk_index >= cork_array_size(&cache->chunks)) {
  91. /* We've filled up all of the existing chunks, and need to
  92. * create a new one. */
  93. DEBUG(" (allocating chunk %zu)",
  94. cork_array_size(&cache->chunks));
  95. struct ipset_node *new_chunk = cork_calloc
  96. (IPSET_BDD_NODE_CACHE_SIZE, sizeof(struct ipset_node));
  97. cork_array_append(&cache->chunks, new_chunk);
  98. }
  99. return next_index;
  100. } else {
  101. /* Reuse a recently freed node. */
  102. ipset_value next_index = cache->free_list;
  103. struct ipset_node *node =
  104. ipset_node_cache_get_nonterminal_by_index(cache, next_index);
  105. cache->free_list = node->refcount;
  106. return next_index;
  107. }
  108. }
  109. ipset_node_id
  110. ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node_id)
  111. {
  112. if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) {
  113. struct ipset_node *node =
  114. ipset_node_cache_get_nonterminal(cache, node_id);
  115. DEBUG(" [incref " IPSET_NODE_ID_FORMAT "]",
  116. IPSET_NODE_ID_VALUES(node_id));
  117. node->refcount++;
  118. }
  119. return node_id;
  120. }
  121. void
  122. ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node_id)
  123. {
  124. if (ipset_node_get_type(node_id) == IPSET_NONTERMINAL_NODE) {
  125. struct ipset_node *node =
  126. ipset_node_cache_get_nonterminal(cache, node_id);
  127. DEBUG(" [decref " IPSET_NODE_ID_FORMAT "]",
  128. IPSET_NODE_ID_VALUES(node_id));
  129. if (--node->refcount == 0) {
  130. DEBUG(" [free " IPSET_NODE_ID_FORMAT "]",
  131. IPSET_NODE_ID_VALUES(node_id));
  132. ipset_node_decref(cache, node->low);
  133. ipset_node_decref(cache, node->high);
  134. cork_hash_table_delete(cache->node_cache, node, NULL, NULL);
  135. /* Add the node to the free list */
  136. node->refcount = cache->free_list;
  137. cache->free_list = ipset_nonterminal_value(node_id);
  138. }
  139. }
  140. }
  141. bool
  142. ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1,
  143. ipset_node_id node_id1,
  144. const struct ipset_node_cache *cache2,
  145. ipset_node_id node_id2)
  146. {
  147. struct ipset_node *node1;
  148. struct ipset_node *node2;
  149. if (ipset_node_get_type(node_id1) != ipset_node_get_type(node_id2)) {
  150. return false;
  151. }
  152. if (ipset_node_get_type(node_id1) == IPSET_TERMINAL_NODE) {
  153. return node_id1 == node_id2;
  154. }
  155. node1 = ipset_node_cache_get_nonterminal(cache1, node_id1);
  156. node2 = ipset_node_cache_get_nonterminal(cache2, node_id2);
  157. return
  158. (node1->variable == node2->variable) &&
  159. ipset_node_cache_nodes_equal(cache1, node1->low, cache2, node2->low) &&
  160. ipset_node_cache_nodes_equal(cache1, node1->high, cache2, node2->high);
  161. }
  162. ipset_node_id
  163. ipset_node_cache_nonterminal(struct ipset_node_cache *cache,
  164. ipset_variable variable,
  165. ipset_node_id low, ipset_node_id high)
  166. {
  167. /* Don't allow any nonterminals whose low and high subtrees are the
  168. * same, since the nonterminal would be redundant. */
  169. if (CORK_UNLIKELY(low == high)) {
  170. DEBUG(" [ SKIP nonterminal(x%u? "
  171. IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]",
  172. variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low));
  173. ipset_node_decref(cache, high);
  174. return low;
  175. }
  176. /* Check to see if there's already a nonterminal with these contents
  177. * in the cache. */
  178. DEBUG(" [search nonterminal(x%u? "
  179. IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT ")]",
  180. variable, IPSET_NODE_ID_VALUES(high), IPSET_NODE_ID_VALUES(low));
  181. struct ipset_node search_node;
  182. search_node.variable = variable;
  183. search_node.low = low;
  184. search_node.high = high;
  185. bool is_new;
  186. struct cork_hash_table_entry *entry =
  187. cork_hash_table_get_or_create
  188. (cache->node_cache, &search_node, &is_new);
  189. if (!is_new) {
  190. /* There's already a node with these contents, so return its ID. */
  191. ipset_node_id node_id = (uintptr_t) entry->value;
  192. DEBUG(" [reuse " IPSET_NODE_ID_FORMAT "]",
  193. IPSET_NODE_ID_VALUES(node_id));
  194. ipset_node_incref(cache, node_id);
  195. ipset_node_decref(cache, low);
  196. ipset_node_decref(cache, high);
  197. return node_id;
  198. } else {
  199. /* This node doesn't exist yet. Allocate a permanent copy of
  200. * the node, add it to the cache, and then return its ID. */
  201. ipset_value new_index = ipset_node_cache_alloc_node(cache);
  202. ipset_node_id new_node_id = ipset_nonterminal_node_id(new_index);
  203. struct ipset_node *real_node =
  204. ipset_node_cache_get_nonterminal_by_index(cache, new_index);
  205. real_node->refcount = 1;
  206. real_node->variable = variable;
  207. real_node->low = low;
  208. real_node->high = high;
  209. entry->key = real_node;
  210. entry->value = (void *) (uintptr_t) new_node_id;
  211. DEBUG(" [new " IPSET_NODE_ID_FORMAT "]",
  212. IPSET_NODE_ID_VALUES(new_node_id));
  213. return new_node_id;
  214. }
  215. }
  216. bool
  217. ipset_bool_array_assignment(const void *user_data, ipset_variable variable)
  218. {
  219. const bool *bool_array = (const bool *) user_data;
  220. return bool_array[variable];
  221. }
  222. bool
  223. ipset_bit_array_assignment(const void *user_data, ipset_variable variable)
  224. {
  225. return IPSET_BIT_GET(user_data, variable);
  226. }
  227. ipset_value
  228. ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node_id,
  229. ipset_assignment_func assignment, const void *user_data)
  230. {
  231. ipset_node_id curr_node_id = node_id;
  232. DEBUG("Evaluating BDD node " IPSET_NODE_ID_FORMAT,
  233. IPSET_NODE_ID_VALUES(node_id));
  234. /* As long as the current node is a nonterminal, we have to check
  235. * the value of the current variable. */
  236. while (ipset_node_get_type(curr_node_id) == IPSET_NONTERMINAL_NODE) {
  237. /* We have to look up this variable in the assignment. */
  238. struct ipset_node *node =
  239. ipset_node_cache_get_nonterminal(cache, curr_node_id);
  240. bool this_value = assignment(user_data, node->variable);
  241. DEBUG("[%3u] Nonterminal " IPSET_NODE_ID_FORMAT,
  242. node->variable, IPSET_NODE_ID_VALUES(curr_node_id));
  243. DEBUG("[%3u] x%u = %s",
  244. node->variable, node->variable, this_value? "TRUE": "FALSE");
  245. if (this_value) {
  246. /* This node's variable is true in the assignment vector, so
  247. * trace down the high subtree. */
  248. curr_node_id = node->high;
  249. } else {
  250. /* This node's variable is false in the assignment vector,
  251. * so trace down the low subtree. */
  252. curr_node_id = node->low;
  253. }
  254. }
  255. /* Once we find a terminal node, we've got the final result. */
  256. DEBUG("Evaluated result is %u", ipset_terminal_value(curr_node_id));
  257. return ipset_terminal_value(curr_node_id);
  258. }
  259. /* A “fake” BDD node given by an assignment. */
  260. struct ipset_fake_node {
  261. ipset_variable current_var;
  262. ipset_variable var_count;
  263. ipset_assignment_func assignment;
  264. const void *user_data;
  265. ipset_value value;
  266. };
  267. /* A fake BDD node representing the terminal 0 value. */
  268. static struct ipset_fake_node fake_terminal_0 = { 0, 0, NULL, 0, 0 };
  269. /* We set elements in a map using the if-then-else (ITE) operator:
  270. *
  271. * new_set = new_element? new_value: old_set
  272. *
  273. * The below is a straight copy of the standard trinary APPLY from the BDD
  274. * literature, but without the caching of the results. And also with the
  275. * wrinkle that the F argument to ITE (i.e., new_element) is given by an
  276. * assignment, and not by a BDD node. (This lets us skip constructing the BDD
  277. * for the assignment, saving us a few cycles.)
  278. */
  279. static ipset_node_id
  280. ipset_apply_ite(struct ipset_node_cache *cache, struct ipset_fake_node *f,
  281. ipset_value g, ipset_node_id h)
  282. {
  283. ipset_node_id h_low;
  284. ipset_node_id h_high;
  285. ipset_node_id result_low;
  286. ipset_node_id result_high;
  287. /* If F is a terminal, then we're in one of the following two
  288. * cases:
  289. *
  290. * 1? G: H == G
  291. * 0? G: H == H
  292. */
  293. if (f->current_var == f->var_count) {
  294. ipset_node_id result;
  295. DEBUG("[%3u] F is terminal (value %u)", f->current_var, f->value);
  296. if (f->value == 0) {
  297. DEBUG("[%3u] 0? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT
  298. " = " IPSET_NODE_ID_FORMAT,
  299. f->current_var,
  300. IPSET_NODE_ID_VALUES(ipset_terminal_node_id(g)),
  301. IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h));
  302. result = ipset_node_incref(cache, h);
  303. } else {
  304. result = ipset_terminal_node_id(g);
  305. DEBUG("[%3u] 1? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT
  306. " = " IPSET_NODE_ID_FORMAT,
  307. f->current_var, IPSET_NODE_ID_VALUES(result),
  308. IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(result));
  309. }
  310. return result;
  311. }
  312. /* F? G: G == G */
  313. if (h == ipset_terminal_node_id(g)) {
  314. DEBUG("[%3u] F? " IPSET_NODE_ID_FORMAT ": " IPSET_NODE_ID_FORMAT
  315. " = " IPSET_NODE_ID_FORMAT,
  316. f->current_var, IPSET_NODE_ID_VALUES(h),
  317. IPSET_NODE_ID_VALUES(h), IPSET_NODE_ID_VALUES(h));
  318. return h;
  319. }
  320. /* From here to the end of the function, we know that F is a
  321. * nonterminal. */
  322. DEBUG("[%3u] F is nonterminal", f->current_var);
  323. /* We're going to do two recursive calls, a “low” one and a “high” one. For
  324. * each nonterminal that has the minimum variable number, we use its low and
  325. * high pointers in the respective recursive call. For all other
  326. * nonterminals, and for all terminals, we use the operand itself. */
  327. if (ipset_node_get_type(h) == IPSET_NONTERMINAL_NODE) {
  328. struct ipset_node *h_node =
  329. ipset_node_cache_get_nonterminal(cache, h);
  330. DEBUG("[%3u] H is nonterminal (variable %u)",
  331. f->current_var, h_node->variable);
  332. if (h_node->variable < f->current_var) {
  333. /* var(F) > var(H), so we only recurse down the H branches. */
  334. DEBUG("[%3u] Recursing only down H", f->current_var);
  335. DEBUG("[%3u] Recursing high", f->current_var);
  336. result_high = ipset_apply_ite(cache, f, g, h_node->high);
  337. DEBUG("[%3u] Back from high recursion", f->current_var);
  338. DEBUG("[%3u] Recursing low", f->current_var);
  339. result_low = ipset_apply_ite(cache, f, g, h_node->low);
  340. DEBUG("[%3u] Back from low recursion", f->current_var);
  341. return ipset_node_cache_nonterminal
  342. (cache, h_node->variable, result_low, result_high);
  343. } else if (h_node->variable == f->current_var) {
  344. /* var(F) == var(H), so we recurse down both branches. */
  345. DEBUG("[%3u] Recursing down both F and H", f->current_var);
  346. h_low = h_node->low;
  347. h_high = h_node->high;
  348. } else {
  349. /* var(F) < var(H), so we only recurse down the F branches. */
  350. DEBUG("[%3u] Recursing only down F", f->current_var);
  351. h_low = h;
  352. h_high = h;
  353. }
  354. } else {
  355. /* H in nonterminal, so we only recurse down the F branches. */
  356. DEBUG("[%3u] H is terminal (value %u)",
  357. f->current_var, ipset_terminal_value(h));
  358. DEBUG("[%3u] Recursing only down F", f->current_var);
  359. h_low = h;
  360. h_high = h;
  361. }
  362. /* F is a “fake” nonterminal node, since it comes from our assignment. One
  363. * of its branches will be the 0 terminal, and the other will be the fake
  364. * nonterminal for the next variable in the assignment. (Which one is low
  365. * and which one is high depends on the value of the current variable in the
  366. * assignment.) */
  367. if (f->assignment(f->user_data, f->current_var)) {
  368. /* The current variable is set in F. The low branch is terminal 0; the
  369. * high branch is the next variable in F. */
  370. DEBUG("[%3u] x[%u] is set", f->current_var, f->current_var);
  371. DEBUG("[%3u] Recursing high", f->current_var);
  372. f->current_var++;
  373. result_high = ipset_apply_ite(cache, f, g, h_high);
  374. f->current_var--;
  375. DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT,
  376. f->current_var, IPSET_NODE_ID_VALUES(result_high));
  377. DEBUG("[%3u] Recursing low", f->current_var);
  378. fake_terminal_0.current_var = f->var_count;
  379. fake_terminal_0.var_count = f->var_count;
  380. result_low = ipset_apply_ite(cache, &fake_terminal_0, g, h_low);
  381. DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT,
  382. f->current_var, IPSET_NODE_ID_VALUES(result_low));
  383. } else {
  384. /* The current variable is NOT set in F. The high branch is terminal 0;
  385. * the low branch is the next variable in F. */
  386. DEBUG("[%3u] x[%u] is NOT set", f->current_var, f->current_var);
  387. DEBUG("[%3u] Recursing high", f->current_var);
  388. fake_terminal_0.current_var = f->var_count;
  389. fake_terminal_0.var_count = f->var_count;
  390. result_high = ipset_apply_ite(cache, &fake_terminal_0, g, h_high);
  391. DEBUG("[%3u] Back from high recursion: " IPSET_NODE_ID_FORMAT,
  392. f->current_var, IPSET_NODE_ID_VALUES(result_high));
  393. DEBUG("[%3u] Recursing low", f->current_var);
  394. f->current_var++;
  395. result_low = ipset_apply_ite(cache, f, g, h_low);
  396. f->current_var--;
  397. DEBUG("[%3u] Back from low recursion: " IPSET_NODE_ID_FORMAT,
  398. f->current_var, IPSET_NODE_ID_VALUES(result_low));
  399. }
  400. return ipset_node_cache_nonterminal
  401. (cache, f->current_var, result_low, result_high);
  402. }
  403. ipset_node_id
  404. ipset_node_insert(struct ipset_node_cache *cache, ipset_node_id node,
  405. ipset_assignment_func assignment, const void *user_data,
  406. ipset_variable var_count, ipset_value value)
  407. {
  408. struct ipset_fake_node f = { 0, var_count, assignment, user_data, 1 };
  409. DEBUG("Inserting new element");
  410. return ipset_apply_ite(cache, &f, value, node);
  411. }