|
|
/* -*- coding: utf-8 -*-
* ---------------------------------------------------------------------- * Copyright © 2010-2013, RedJack, LLC. * All rights reserved. * * Please see the LICENSE.txt file in this distribution for license * details. * ---------------------------------------------------------------------- */
#include <libcork/core.h>
#include <libcork/helpers/errors.h>
#include "ipset/bdd/nodes.h"
#include "ipset/logging.h"
/*-----------------------------------------------------------------------
* Generic saving logic */
/**
* On disk, we use a different node ID scheme than we do in memory. * Terminal node IDs are non-negative, and are equal to the terminal * value. Nonterminal node IDs are negative, starting with -1. * Nonterminal -1 appears first on disk, then nonterminal -2, and so * on. */
typedef int serialized_id;
/* forward declaration */ struct save_data;
/**
* A callback that outputs any necessary header. Should return an int * status code indicating whether the write was successful. */
typedef int (*write_header_func)(struct save_data *save_data, struct ipset_node_cache *cache, ipset_node_id root);
/**
* A callback that outputs any necessary footer. Should return an int * status code indicating whether the write was successful. */
typedef int (*write_footer_func)(struct save_data *save_data, struct ipset_node_cache *cache, ipset_node_id root);
/**
* A callback that actually outputs a terminal node to disk. Should * return an int status code indicating whether the write was successful. */
typedef int (*write_terminal_func)(struct save_data *save_data, ipset_value terminal_value);
/**
* A callback that actually outputs a nonterminal node to disk. * Should return an int status code indicating whether the write was * successful. */
typedef int (*write_nonterminal_func)(struct save_data *save_data, serialized_id serialized_node, ipset_variable variable, serialized_id serialized_low, serialized_id serialized_high);
/**
* A helper struct containing all of the persistent data items needed * during the execution of a save. */
struct save_data { /* The node cache that we're saving nodes from. */ struct ipset_node_cache *cache;
/* The output stream to save the data to. */ struct cork_stream_consumer *stream;
/* The cache of serialized IDs for any nonterminals that we've
* encountered so far. */ struct cork_hash_table *serialized_ids;
/* The serialized ID to use for the next nonterminal that we
* encounter. */ serialized_id next_serialized_id;
/* The callback used to write the file header to the stream. */ write_header_func write_header;
/* The callback used to write the file footer to the stream. */ write_footer_func write_footer;
/* The callback used to write terminals to the stream. */ write_terminal_func write_terminal;
/* The callback used to write nonterminals to the stream. */ write_nonterminal_func write_nonterminal;
/* A pointer to any additional data needed by the callbacks. */ void *user_data; };
/**
* A helper function for ipset_node_save(). Outputs a nonterminal * node in a BDD tree, if we haven't done so already. Ensures that * the children of the nonterminal are output before the nonterminal * is. Returns the serialized ID of this node. */
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 save_bdd(struct save_data *save_data, struct ipset_node_cache *cache, ipset_node_id root) { /* First, output the file header. */
DEBUG("Writing file header"); rii_check(save_data->write_header(save_data, cache, root));
/* The serialized node IDs are different than the in-memory node
* IDs. This means that, for our nonterminal nodes, we need a * mapping from internal node ID to serialized node ID. */
DEBUG("Creating file caches"); save_data->serialized_ids = cork_pointer_hash_table_new(0, 0); save_data->next_serialized_id = -1;
/* Trace down through the BDD tree, outputting each terminal and
* nonterminal node as they're encountered. */
DEBUG("Writing nodes");
serialized_id last_serialized_id; ei_check(save_visit_node(save_data, root, &last_serialized_id));
/* Finally, output the file footer and cleanup. */
DEBUG("Writing file footer"); ei_check(save_data->write_footer(save_data, cache, root));
DEBUG("Freeing file caches"); cork_hash_table_free(save_data->serialized_ids); return 0;
error: /* If there's an error, clean up the objects that we've created
* before returning. */ cork_hash_table_free(save_data->serialized_ids); return -1; }
/*-----------------------------------------------------------------------
* Helper functions */
/**
* Write a NUL-terminated string to a stream. If we can't write the * string for some reason, return an error. */ static int write_string(struct cork_stream_consumer *stream, const char *str) { size_t len = strlen(str); return cork_stream_consumer_data(stream, str, len, false); }
/**
* Write a big-endian uint8 to a stream. If we can't write the * integer for some reason, return an error. */ static int write_uint8(struct cork_stream_consumer *stream, uint8_t val) { /* for a byte, we don't need to endian-swap */ return cork_stream_consumer_data(stream, &val, sizeof(uint8_t), false); }
/**
* Write a big-endian uint16 to a stream. If we can't write the * integer for some reason, return an error. */ static int write_uint16(struct cork_stream_consumer *stream, uint16_t val) { CORK_UINT16_HOST_TO_BIG_IN_PLACE(val); return cork_stream_consumer_data(stream, &val, sizeof(uint16_t), false); }
/**
* Write a big-endian uint32 to a stream. If we can't write the * integer for some reason, return an error. */
static int write_uint32(struct cork_stream_consumer *stream, uint32_t val) { CORK_UINT32_HOST_TO_BIG_IN_PLACE(val); return cork_stream_consumer_data(stream, &val, sizeof(uint32_t), false); }
/**
* Write a big-endian uint64 to a stream. If we can't write the * integer for some reason, return an error. */
static int write_uint64(struct cork_stream_consumer *stream, uint64_t val) { CORK_UINT64_HOST_TO_BIG_IN_PLACE(val); return cork_stream_consumer_data(stream, &val, sizeof(uint64_t), false); }
/*-----------------------------------------------------------------------
* V1 BDD file */
static const char MAGIC_NUMBER[] = "IP set"; static const size_t MAGIC_NUMBER_LENGTH = sizeof(MAGIC_NUMBER) - 1;
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 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; }
static int write_terminal_v1(struct save_data *save_data, ipset_value terminal_value) { /* We don't have to write anything out for a terminal in a V1 file,
* since the terminal's value will be encoded into the node ID * wherever it's used. */ return 0; }
static int write_nonterminal_v1(struct save_data *save_data, serialized_id serialized_node, ipset_variable variable, serialized_id serialized_low, serialized_id serialized_high) { rii_check(write_uint8(save_data->stream, variable)); rii_check(write_uint32(save_data->stream, serialized_low)); rii_check(write_uint32(save_data->stream, serialized_high)); return 0; }
int ipset_node_cache_save(struct cork_stream_consumer *stream, struct ipset_node_cache *cache, ipset_node_id node) { struct save_data save_data; save_data.cache = cache; save_data.stream = stream; save_data.write_header = write_header_v1; save_data.write_footer = write_footer_v1; save_data.write_terminal = write_terminal_v1; save_data.write_nonterminal = write_nonterminal_v1; return save_bdd(&save_data, cache, node); }
/*-----------------------------------------------------------------------
* GraphViz dot file */
static const char *GRAPHVIZ_HEADER = "strict digraph bdd {\n";
static const char *GRAPHVIZ_FOOTER = "}\n";
struct dot_data { /* The terminal value to leave out of the dot file. This should be
* the default value of the set or map. */ ipset_value default_value;
/* A scratch buffer */ struct cork_buffer scratch; };
static int write_header_dot(struct save_data *save_data, struct ipset_node_cache *cache, ipset_node_id root) { /* Output the opening clause of the GraphViz script. */ rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true)); return write_string(save_data->stream, GRAPHVIZ_HEADER); }
static int write_footer_dot(struct save_data *save_data, struct ipset_node_cache *cache, ipset_node_id root) { /* Output the closing clause of the GraphViz script. */ return write_string(save_data->stream, GRAPHVIZ_FOOTER); }
static int write_terminal_dot(struct save_data *save_data, ipset_value terminal_value) { struct dot_data *dot_data = save_data->user_data;
/* If this terminal has the default value, skip it. */ if (terminal_value == dot_data->default_value) { return 0; }
/* Output a node for the terminal value. */ cork_buffer_printf (&dot_data->scratch, " t%d [shape=box, label=%d];\n", terminal_value, terminal_value); return write_string(save_data->stream, dot_data->scratch.buf); }
static int write_nonterminal_dot(struct save_data *save_data, serialized_id serialized_node, ipset_variable variable, serialized_id serialized_low, serialized_id serialized_high) { struct dot_data *dot_data = save_data->user_data;
/* Include a node for the nonterminal value. */ cork_buffer_printf (&dot_data->scratch, " n%d [shape=circle,label=%u];\n", (-serialized_node), variable);
/* Include an edge for the low pointer. */ if (serialized_low < 0) { /* The low pointer is a nonterminal. */ cork_buffer_append_printf (&dot_data->scratch, " n%d -> n%d", (-serialized_node), (-serialized_low)); } else { /* The low pointer is a terminal. */ ipset_value low_value = (ipset_value) serialized_low;
if (low_value == dot_data->default_value) { /* The terminal is the default value, so instead of a real
* terminal, connect this pointer to a dummy circle node. */ cork_buffer_append_printf (&dot_data->scratch, " low%d [shape=circle,label=\"\"]\n" " n%d -> low%d", (-serialized_node), (-serialized_node), (-serialized_node)); } else { /* The terminal isn't a default, so go ahead and output it. */ cork_buffer_append_printf (&dot_data->scratch, " n%d -> t%d", (-serialized_node), serialized_low); } }
cork_buffer_append_printf (&dot_data->scratch, " [style=dashed,color=red]\n");
/* Include an edge for the high pointer. */ if (serialized_high < 0) { /* The high pointer is a nonterminal. */ cork_buffer_append_printf (&dot_data->scratch, " n%d -> n%d", (-serialized_node), (-serialized_high)); } else { /* The high pointer is a terminal. */ ipset_value high_value = (ipset_value) serialized_high;
if (high_value == dot_data->default_value) { /* The terminal is the default value, so instead of a real
* terminal, connect this pointer to a dummy circle node. */ cork_buffer_append_printf (&dot_data->scratch, " high%d " "[shape=circle," "fixedsize=true," "height=0.25," "width=0.25," "label=\"\"]\n" " n%d -> high%d", (-serialized_node), (-serialized_node), (-serialized_node)); } else { /* The terminal isn't a default, so go ahead and output it. */ cork_buffer_append_printf (&dot_data->scratch, " n%d -> t%d", (-serialized_node), serialized_high); } }
cork_buffer_append_printf (&dot_data->scratch, " [style=solid,color=black]\n");
/* Output the clauses to the stream. */ return write_string(save_data->stream, dot_data->scratch.buf); }
int ipset_node_cache_save_dot(struct cork_stream_consumer *stream, struct ipset_node_cache *cache, ipset_node_id node) { struct dot_data dot_data = { 0, /* default value */ {NULL, 0, 0} };
struct save_data save_data; save_data.cache = cache; save_data.stream = stream; save_data.write_header = write_header_dot; save_data.write_footer = write_footer_dot; save_data.write_terminal = write_terminal_dot; save_data.write_nonterminal = write_nonterminal_dot; save_data.user_data = &dot_data; return save_bdd(&save_data, cache, node); }
|