mtrie.cpp 12.6 KB
Newer Older
1
/*
2
    Copyright (c) 2007-2014 Contributors as noted in the AUTHORS file
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

    This file is part of 0MQ.

    0MQ is free software; you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License as published by
    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
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdlib.h>

#include <new>
#include <algorithm>

#include "platform.hpp"
#if defined ZMQ_HAVE_WINDOWS
#include "windows.hpp"
#endif

#include "err.hpp"
#include "pipe.hpp"
#include "mtrie.hpp"

zmq::mtrie_t::mtrie_t () :
35
    pipes (0),
36
    min (0),
37 38
    count (0),
    live_nodes (0)
39 40 41 42 43
{
}

zmq::mtrie_t::~mtrie_t ()
{
44 45 46 47 48
    if (pipes) {
        delete pipes;
        pipes = 0;
    }

49 50
    if (count == 1) {
        zmq_assert (next.node);
51
        delete next.node;
52 53
        next.node = 0;
    }
54 55
    else 
    if (count > 1) {
56
        for (unsigned short i = 0; i != count; ++i)
57
            delete next.table [i];
58 59 60 61 62
        free (next.table);
    }
}

bool zmq::mtrie_t::add (unsigned char *prefix_, size_t size_, pipe_t *pipe_)
63 64 65 66 67 68
{
    return add_helper (prefix_, size_, pipe_);
}

bool zmq::mtrie_t::add_helper (unsigned char *prefix_, size_t size_,
    pipe_t *pipe_)
69 70 71
{
    //  We are at the node corresponding to the prefix. We are done.
    if (!size_) {
72
        bool result = !pipes;
73 74 75 76
        if (!pipes) {
            pipes = new (std::nothrow) pipes_t;
            alloc_assert (pipes);
        }
77
        pipes->insert (pipe_);
78 79 80 81 82 83 84 85 86 87 88 89 90
        return result;
    }

    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;
        }
91 92
        else 
        if (count == 1) {
93 94 95 96 97
            unsigned char oldc = min;
            mtrie_t *oldp = next.node;
            count = (min < c ? c - min : min - c) + 1;
            next.table = (mtrie_t**)
                malloc (sizeof (mtrie_t*) * count);
98
            alloc_assert (next.table);
99 100 101 102 103
            for (unsigned short i = 0; i != count; ++i)
                next.table [i] = 0;
            min = std::min (min, c);
            next.table [oldc - min] = oldp;
        }
104 105
        else 
        if (min < c) {
106 107 108
            //  The new character is above the current character range.
            unsigned short old_count = count;
            count = c - min + 1;
109
            next.table = (mtrie_t**) realloc (next.table,
110
                sizeof (mtrie_t*) * count);
111
            alloc_assert (next.table);
112 113 114 115 116 117 118
            for (unsigned short i = old_count; i != count; i++)
                next.table [i] = NULL;
        }
        else {
            //  The new character is below the current character range.
            unsigned short old_count = count;
            count = (min + old_count) - c;
119
            next.table = (mtrie_t**) realloc (next.table,
120
                sizeof (mtrie_t*) * count);
121
            alloc_assert (next.table);
122 123 124 125 126 127 128 129 130 131 132 133
            memmove (next.table + min - c, next.table,
                old_count * sizeof (mtrie_t*));
            for (unsigned short i = 0; i != min - c; i++)
                next.table [i] = NULL;
            min = c;
        }
    }

    //  If next node does not exist, create one.
    if (count == 1) {
        if (!next.node) {
            next.node = new (std::nothrow) mtrie_t;
134
            alloc_assert (next.node);
135
            ++live_nodes;
136
        }
137
        return next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
138 139 140 141
    }
    else {
        if (!next.table [c - min]) {
            next.table [c - min] = new (std::nothrow) mtrie_t;
142
            alloc_assert (next.table [c - min]);
143
            ++live_nodes;
144
        }
145
        return next.table [c - min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    }
}


void zmq::mtrie_t::rm (pipe_t *pipe_,
    void (*func_) (unsigned char *data_, size_t size_, void *arg_),
    void *arg_)
{
    unsigned char *buff = NULL;
    rm_helper (pipe_, &buff, 0, 0, func_, arg_);
    free (buff);
}

void zmq::mtrie_t::rm_helper (pipe_t *pipe_, unsigned char **buff_,
    size_t buffsize_, size_t maxbuffsize_,
    void (*func_) (unsigned char *data_, size_t size_, void *arg_),
    void *arg_)
{
    //  Remove the subscription from this node.
165
    if (pipes && pipes->erase (pipe_) && pipes->empty ()) {
166
        func_ (*buff_, buffsize_, arg_);
167 168 169
        delete pipes;
        pipes = 0;
    }
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

    //  Adjust the buffer.
    if (buffsize_ >= maxbuffsize_) {
        maxbuffsize_ = buffsize_ + 256;
        *buff_ = (unsigned char*) realloc (*buff_, maxbuffsize_);
        alloc_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->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_,
            func_, arg_);
188 189

        //  Prune the node if it was made redundant by the removal
190 191 192
        if (next.node->is_redundant ()) {
            delete next.node;
            next.node = 0;
193
            count = 0;
194
            --live_nodes;
195
            zmq_assert (live_nodes == 0);
196
        }
197 198 199 200
        return;
    }

    //  If there are multiple subnodes.
201 202
    //
    //  New min non-null character in the node table after the removal
203
    unsigned char new_min = min + count - 1;
204
    //  New max non-null character in the node table after the removal
205
    unsigned char new_max = min;
206
    for (unsigned short c = 0; c != count; c++) {
207
        (*buff_) [buffsize_] = min + c;
208
        if (next.table [c]) {
209 210
            next.table [c]->rm_helper (pipe_, buff_, buffsize_ + 1,
                maxbuffsize_, func_, arg_);
211

212
            //  Prune redundant nodes from the mtrie
213 214 215
            if (next.table [c]->is_redundant ()) {
                delete next.table [c];
                next.table [c] = 0;
216 217

                zmq_assert (live_nodes > 0);
218 219
                --live_nodes;
            }
220
            else {
221 222 223 224 225 226 227 228
                //  The node is not redundant, so it's a candidate for being
                //  the new min/max node.
                //
                //  We loop through the node array from left to right, so the
                //  first non-null, non-redundant node encountered is the new
                //  minimum index. Conversely, the last non-redundant, non-null
                //  node encountered is the new maximum index.
                if (c + min < new_min)
229
                    new_min = c + min;
230
                if (c + min > new_max)
231 232
                    new_max = c + min;
            }
233 234
        }
    }
235 236 237

    zmq_assert (count > 1);

238 239 240 241 242 243
    //  Free the node table if it's no longer used.
    if (live_nodes == 0) {
        free (next.table);
        next.table = NULL;
        count = 0;
    }
244
    //  Compact the node table if possible
245 246
    else 
    if (live_nodes == 1) {
247 248 249 250 251 252 253 254 255 256 257 258
        //  If there's only one live node in the table we can
        //  switch to using the more compact single-node
        //  representation
        zmq_assert (new_min == new_max);
        zmq_assert (new_min >= min && new_min < min + count);
        mtrie_t *node = next.table [new_min - min];
        zmq_assert (node);
        free (next.table);
        next.node = node;
        count = 1;
        min = new_min;
    }
259 260
    else
    if (new_min > min || new_max < min + count - 1) {
261 262 263 264 265 266 267 268 269 270
        zmq_assert (new_max - new_min + 1 > 1);

        mtrie_t **old_table = next.table;
        zmq_assert (new_min > min || new_max < min + count - 1);
        zmq_assert (new_min >= min);
        zmq_assert (new_max <= min + count - 1);
        zmq_assert (new_max - new_min + 1 < count);

        count = new_max - new_min + 1;
        next.table = (mtrie_t**) malloc (sizeof (mtrie_t*) * count);
271
        alloc_assert (next.table);
272 273 274 275 276 277 278

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

        min = new_min;
    }
279 280 281 282
}

bool zmq::mtrie_t::rm (unsigned char *prefix_, size_t size_, pipe_t *pipe_)
{
283 284
    return rm_helper (prefix_, size_, pipe_);
}
285

286 287 288 289
bool zmq::mtrie_t::rm_helper (unsigned char *prefix_, size_t size_,
    pipe_t *pipe_)
{
    if (!size_) {
290 291 292 293 294 295 296 297 298
        if (pipes) {
            pipes_t::size_type erased = pipes->erase (pipe_);
            zmq_assert (erased == 1);
            if (pipes->empty ()) {
                delete pipes;
                pipes = 0;
            }
        }
        return !pipes;
299 300 301 302 303
    }

    unsigned char c = *prefix_;
    if (!count || c < min || c >= min + count)
        return false;
304

305 306
    mtrie_t *next_node =
        count == 1 ? next.node : next.table [c - min];
307

308 309
    if (!next_node)
        return false;
310

311 312 313 314
    bool ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);

    if (next_node->is_redundant ()) {
        delete next_node;
315 316
        zmq_assert (count > 0);

317
        if (count == 1) {
318
            next.node = 0;
319
            count = 0;
320 321
            --live_nodes;
            zmq_assert (live_nodes == 0);
322
        }
323
        else {
324
            next.table [c - min] = 0;
325 326 327 328 329 330 331 332
            zmq_assert (live_nodes > 1);
            --live_nodes;

            //  Compact the table if possible
            if (live_nodes == 1) {
                //  If there's only one live node in the table we can
                //  switch to using the more compact single-node
                //  representation
333 334 335
                unsigned short i;
                for (i = 0; i < count; ++i)
                    if (next.table [i])
336 337
                        break;

338 339
                zmq_assert (i < count);
                min += i;
340
                count = 1;
341 342 343
                mtrie_t *oldp = next.table [i];
                free (next.table);
                next.node = oldp;
344
            }
345 346
            else
            if (c == min) {
347
                //  We can compact the table "from the left"
348 349 350
                unsigned short i;
                for (i = 1; i < count; ++i)
                    if (next.table [i])
351 352
                        break;

353 354 355
                zmq_assert (i < count);
                min += i;
                count -= i;
356 357
                mtrie_t **old_table = next.table;
                next.table = (mtrie_t**) malloc (sizeof (mtrie_t*) * count);
358
                alloc_assert (next.table);
359
                memmove (next.table, old_table + i, sizeof (mtrie_t*) * count);
360 361
                free (old_table);
            }
362 363
            else
            if (c == min + count - 1) {
364
                //  We can compact the table "from the right"
365 366 367
                unsigned short i;
                for (i = 1; i < count; ++i)
                    if (next.table [count - 1 - i])
368 369
                        break;

370 371
                zmq_assert (i < count);
                count -= i;
372 373
                mtrie_t **old_table = next.table;
                next.table = (mtrie_t**) malloc (sizeof (mtrie_t*) * count);
374
                alloc_assert (next.table);
375 376 377 378
                memmove (next.table, old_table, sizeof (mtrie_t*) * count);
                free (old_table);
            }
        }
379 380 381
    }

    return ret;
382 383
}

384 385
void zmq::mtrie_t::match (unsigned char *data_, size_t size_,
    void (*func_) (pipe_t *pipe_, void *arg_), void *arg_)
386
{
387
    mtrie_t *current = this;
388
    while (true) {
389 390

        //  Signal the pipes attached to this node.
391 392 393 394 395
        if (current->pipes) {
            for (pipes_t::iterator it = current->pipes->begin ();
                  it != current->pipes->end (); ++it)
                func_ (*it, arg_);
        }
396

397 398 399 400
        //  If we are at the end of the message, there's nothing more to match.
        if (!size_)
            break;

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
        //  If there are no subnodes in the trie, return.
        if (current->count == 0)
            break;

        //  If there's one subnode (optimisation).
		if (current->count == 1) {
            if (data_ [0] != current->min)
                break;
            current = current->next.node;
            data_++;
            size_--;
		    continue;
		}

		//  If there are multiple subnodes.
416 417
        if (data_ [0] < current->min || data_ [0] >=
              current->min + current->count)
418
            break;
419
        if (!current->next.table [data_ [0] - current->min])
420
            break;
421
        current = current->next.table [data_ [0] - current->min];
422 423
        data_++;
        size_--;
424 425 426
    }
}

427
bool zmq::mtrie_t::is_redundant () const
428
{
429
    return !pipes && live_nodes == 0;
430
}