trie.cpp 10.2 KB
Newer Older
1
/*
2
    Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file
3 4 5 6

    This file is part of 0MQ.

    0MQ is free software; you can redistribute it and/or modify it under
7
    the terms of the GNU Lesser General Public License as published by
8 9 10 11 12 13
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    0MQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU Lesser General Public License for more details.
15

16
    You should have received a copy of the GNU Lesser General Public License
17 18 19 20 21 22 23 24
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>

#include <new>
#include <algorithm>

25 26 27 28 29
#include "platform.hpp"
#if defined ZMQ_HAVE_WINDOWS
#include "windows.hpp"
#endif

30
#include "err.hpp"
31
#include "trie.hpp"
32

33
zmq::trie_t::trie_t () :
34 35
    refcnt (0),
    min (0),
36 37
    count (0),
    live_nodes (0)
38 39 40
{
}

41
zmq::trie_t::~trie_t ()
42
{
43 44
    if (count == 1) {
        zmq_assert (next.node);
45
        delete next.node;
46 47
        next.node = 0;
    }
48 49
    else
    if (count > 1) {
50
        for (unsigned short i = 0; i != count; ++i)
51
            delete next.table [i];
52 53 54 55
        free (next.table);
    }
}

56
bool zmq::trie_t::add (unsigned char *prefix_, size_t size_)
57 58 59 60
{
    //  We are at the node corresponding to the prefix. We are done.
    if (!size_) {
        ++refcnt;
61
        return refcnt == 1;
62 63 64 65 66 67 68 69 70 71 72 73
    }

    unsigned char c = *prefix_;
    if (c < min || c >= min + count) {

        //  The character is out of range of currently handled
        //  charcters. We have to extend the table.
        if (!count) {
            min = c;
            count = 1;
            next.node = NULL;
        }
74 75
        else
        if (count == 1) {
76
            unsigned char oldc = min;
77
            trie_t *oldp = next.node;
78
            count = (min < c ? c - min : min - c) + 1;
79 80
            next.table = (trie_t**)
                malloc (sizeof (trie_t*) * count);
81
            alloc_assert (next.table);
82
            for (unsigned short i = 0; i != count; ++i)
83 84 85 86
                next.table [i] = 0;
            min = std::min (min, c);
            next.table [oldc - min] = oldp;
        }
87 88
        else
        if (min < c) {
89
            //  The new character is above the current character range.
90
            unsigned short old_count = count;
91
            count = c - min + 1;
92 93
            next.table = (trie_t**) realloc ((void*) next.table,
                sizeof (trie_t*) * count);
94
            zmq_assert (next.table);
95
            for (unsigned short i = old_count; i != count; i++)
96 97 98 99 100
                next.table [i] = NULL;
        }
        else {

            //  The new character is below the current character range.
101
            unsigned short old_count = count;
102
            count = (min + old_count) - c;
103 104
            next.table = (trie_t**) realloc ((void*) next.table,
                sizeof (trie_t*) * count);
105 106
            zmq_assert (next.table);
            memmove (next.table + min - c, next.table,
107
                old_count * sizeof (trie_t*));
108
            for (unsigned short i = 0; i != min - c; i++)
109 110 111 112 113 114 115 116
                next.table [i] = NULL;
            min = c;
        }
    }

    //  If next node does not exist, create one.
    if (count == 1) {
        if (!next.node) {
117
            next.node = new (std::nothrow) trie_t;
118
            alloc_assert (next.node);
119
            ++live_nodes;
120
            zmq_assert (live_nodes == 1);
121
        }
122
        return next.node->add (prefix_ + 1, size_ - 1);
123 124 125
    }
    else {
        if (!next.table [c - min]) {
126
            next.table [c - min] = new (std::nothrow) trie_t;
127
            alloc_assert (next.table [c - min]);
128
            ++live_nodes;
129
            zmq_assert (live_nodes > 1);
130
        }
131
        return next.table [c - min]->add (prefix_ + 1, size_ - 1);
132 133 134
    }
}

135
bool zmq::trie_t::rm (unsigned char *prefix_, size_t size_)
136
{
137 138 139 140 141 142 143 144 145 146
    //  TODO: Shouldn't an error be reported if the key does not exist?
    if (!size_) {
        if (!refcnt)
            return false;
        refcnt--;
        return refcnt == 0;
    }
    unsigned char c = *prefix_;
    if (!count || c < min || c >= min + count)
        return false;
147

148 149
    trie_t *next_node =
        count == 1 ? next.node : next.table [c - min];
150

151 152
    if (!next_node)
        return false;
153

154
    bool ret = next_node->rm (prefix_ + 1, size_ - 1);
155

156 157 158 159
    //  Prune redundant nodes
    if (next_node->is_redundant ()) {
        delete next_node;
        zmq_assert (count > 0);
160

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        if (count == 1) {
            //  The just pruned node is was the only live node
            next.node = 0;
            count = 0;
            --live_nodes;
            zmq_assert (live_nodes == 0);
        }
        else {
            next.table [c - min] = 0;
            zmq_assert (live_nodes > 1);
            --live_nodes;

            //  Compact the table if possible
            if (live_nodes == 1) {
                //  We can switch to using the more compact single-node
                //  representation since the table only contains one live node
                trie_t *node = 0;
                //  Since we always compact the table the pruned node must
                //  either be the left-most or right-most ptr in the node
                //  table
                if (c == min) {
                    //  The pruned node is the left-most node ptr in the
                    //  node table => keep the right-most node
                    node = next.table [count - 1];
                    min += count - 1;
                }
                else
                if (c == min + count - 1) {
                    //  The pruned node is the right-most node ptr in the
                    //  node table => keep the left-most node
                    node = next.table [0];
                }
                zmq_assert (node);
                free (next.table);
                next.node = node;
                count = 1;
            }
            else
            if (c == min) {
                //  We can compact the table "from the left".
                //  Find the left-most non-null node ptr, which we'll use as
                //  our new min
                unsigned char new_min = min;
                for (unsigned short i = 1; i < count; ++i) {
                    if (next.table [i]) {
                        new_min = i + min;
                        break;
                    }
                }
                zmq_assert (new_min != min);

                trie_t **old_table = next.table;
                zmq_assert (new_min > min);
                zmq_assert (count > new_min - min);

                count = count - (new_min - min);
                next.table = (trie_t**) malloc (sizeof (trie_t*) * count);
                alloc_assert (next.table);

                memmove (next.table, old_table + (new_min - min),
                        sizeof (trie_t*) * count);
                free (old_table);

                min = new_min;
            }
            else
            if (c == min + count - 1) {
                //  We can compact the table "from the right".
                //  Find the right-most non-null node ptr, which we'll use to
                //  determine the new table size
                unsigned short new_count = count;
                for (unsigned short i = 1; i < count; ++i) {
                    if (next.table [count - 1 - i]) {
                        new_count = count - i;
                        break;
                    }
                }
                zmq_assert (new_count != count);
                count = new_count;

                trie_t **old_table = next.table;
                next.table = (trie_t**) malloc (sizeof (trie_t*) * count);
                alloc_assert (next.table);

                memmove (next.table, old_table, sizeof (trie_t*) * count);
                free (old_table);
            }
        }
    }
    return ret;
251 252
}

253
bool zmq::trie_t::check (unsigned char *data_, size_t size_)
254 255 256
{
    //  This function is on critical path. It deliberately doesn't use
    //  recursion to get a bit better performance.
257
    trie_t *current = this;
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    while (true) {

        //  We've found a corresponding subscription!
        if (current->refcnt)
            return true;

        //  We've checked all the data and haven't found matching subscription.
        if (!size_)
            return false;

        //  If there's no corresponding slot for the first character
        //  of the prefix, the message does not match.
        unsigned char c = *data_;
        if (c < current->min || c >= current->min + current->count)
            return false;

        //  Move to the next character.
        if (current->count == 1)
            current = current->next.node;
        else {
            current = current->next.table [c - current->min];
            if (!current)
                return false;
        }
        data_++;
        size_--;
    }
}
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

void zmq::trie_t::apply (void (*func_) (unsigned char *data_, size_t size_,
    void *arg_), void *arg_)
{
    unsigned char *buff = NULL;
    apply_helper (&buff, 0, 0, func_, arg_);
    free (buff);
}

void zmq::trie_t::apply_helper (
    unsigned char **buff_, size_t buffsize_, size_t maxbuffsize_,
    void (*func_) (unsigned char *data_, size_t size_, void *arg_), void *arg_)
{
    //  If this node is a subscription, apply the function.
    if (refcnt)
        func_ (*buff_, buffsize_, arg_);

    //  Adjust the buffer.
    if (buffsize_ >= maxbuffsize_) {
        maxbuffsize_ = buffsize_ + 256;
        *buff_ = (unsigned char*) realloc (*buff_, maxbuffsize_);
        zmq_assert (*buff_);
    }

    //  If there are no subnodes in the trie, return.
    if (count == 0)
        return;

    //  If there's one subnode (optimisation).
    if (count == 1) {
        (*buff_) [buffsize_] = min;
        buffsize_++;
        next.node->apply_helper (buff_, buffsize_, maxbuffsize_, func_, arg_);
        return;
    }

    //  If there are multiple subnodes.
323
    for (unsigned short c = 0; c != count; c++) {
324 325 326 327
        (*buff_) [buffsize_] = min + c;
        if (next.table [c])
            next.table [c]->apply_helper (buff_, buffsize_ + 1, maxbuffsize_,
                func_, arg_);
328
    }
329 330
}

331
bool zmq::trie_t::is_redundant () const
332 333 334
{
    return refcnt == 0 && live_nodes == 0;
}