rax-radix-tree 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +574 -0
- package/benchmark/01.match-equals.js +21 -0
- package/benchmark/02.match-suffix.js +20 -0
- package/benchmark/03.match-prefix.js +28 -0
- package/benchmark/04.match-equals-prefix-suffix.js +36 -0
- package/benchmark/05.match-hosts.js +25 -0
- package/benchmark/06.match-wildcard-hosts.js +25 -0
- package/benchmark/07.match-remote-address.js +40 -0
- package/benchmark/08.match-exprs-headers.js +50 -0
- package/benchmark/09.match-exprs-args.js +50 -0
- package/benchmark/run.sh +3 -0
- package/benchmark/utility.js +56 -0
- package/binding.gyp +14 -0
- package/http-radix-tree.js +150 -0
- package/index.js +5 -0
- package/package.json +38 -0
- package/radix-tree.js +284 -0
- package/src/radix_tree.c +169 -0
- package/src/radix_tree.h +39 -0
- package/src/rax/COPYING +9 -0
- package/src/rax/Makefile +26 -0
- package/src/rax/README.md +488 -0
- package/src/rax/crc16.c +88 -0
- package/src/rax/rax-oom-test.c +175 -0
- package/src/rax/rax-test.c +1065 -0
- package/src/rax/rax.c +1996 -0
- package/src/rax/rax.h +217 -0
- package/src/rax/rax_malloc.h +43 -0
- package/src/rax/rax_oom_malloc.h +63 -0
- package/src/rax/rc4rand.c +77 -0
- package/src/rax/rc4rand.h +9 -0
- package/src/rax_wrapper.cpp +238 -0
- package/test/00.path.test.js +84 -0
- package/test/10.tree.test.js +217 -0
- package/test/20.http-tree.test.js +316 -0
package/radix-tree.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const { RadixTree: RaxRadixTree } = require('./build/Release/rax_radix_tree.node');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
const MATCH_TYPE_EQUALS = 'equals';
|
|
5
|
+
const MATCH_TYPE_PREFIX = 'prefix';
|
|
6
|
+
const MATCH_TYPE_SUFFIX = 'suffix';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse path, path supports several patterns:
|
|
10
|
+
* 1. Without '*', full match
|
|
11
|
+
* 2. With a single '*', '*' matches any text not containing '/'
|
|
12
|
+
* 3. With consecutive '**', '**' matches any text
|
|
13
|
+
* Parsing function, the parsed result includes the following parts:
|
|
14
|
+
* treePath: The part before the first '*'
|
|
15
|
+
* path: The input path
|
|
16
|
+
* matchType: Match type, equals => path without '*'. prefix => path with '*'
|
|
17
|
+
* pathPattern: Pattern used for secondary regex matching
|
|
18
|
+
* @param {string} path
|
|
19
|
+
* @returns {Object} Parsing result
|
|
20
|
+
*/
|
|
21
|
+
function parsePath(path) {
|
|
22
|
+
const originPath = path;
|
|
23
|
+
let matchType = MATCH_TYPE_EQUALS;
|
|
24
|
+
let treePath = path;
|
|
25
|
+
let pathPattern = undefined;
|
|
26
|
+
|
|
27
|
+
const starPattern = /(\*\*|\*)/g;
|
|
28
|
+
let parts = undefined;
|
|
29
|
+
if (path.startsWith("*")) {
|
|
30
|
+
matchType = MATCH_TYPE_SUFFIX;
|
|
31
|
+
path = path.split('').reverse().join('');
|
|
32
|
+
parts = path.split(starPattern);
|
|
33
|
+
} else {
|
|
34
|
+
parts = path.split(starPattern);
|
|
35
|
+
matchType = parts.length > 1 ? MATCH_TYPE_PREFIX : MATCH_TYPE_EQUALS;
|
|
36
|
+
}
|
|
37
|
+
treePath = parts[0];
|
|
38
|
+
if (matchType === MATCH_TYPE_PREFIX || matchType === MATCH_TYPE_SUFFIX) {
|
|
39
|
+
pathPattern = '^' + parts.map((part, index) => {
|
|
40
|
+
if (part === '**') {
|
|
41
|
+
return '.*';
|
|
42
|
+
} else if (part === '*') {
|
|
43
|
+
return '[^/]*';
|
|
44
|
+
}
|
|
45
|
+
return part.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
|
|
46
|
+
}).join('') + '$';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
treePath: treePath,
|
|
51
|
+
path: originPath,
|
|
52
|
+
pathPattern: pathPattern,
|
|
53
|
+
matchType: matchType,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class RadixTree {
|
|
58
|
+
constructor(routes, matchFunc) {
|
|
59
|
+
this.originRoutes = routes
|
|
60
|
+
// Initialize routes array with undefined at index 0 to ensure indexing starts from 1
|
|
61
|
+
this.routes = [undefined]
|
|
62
|
+
this.prefixTree = new RaxRadixTree() // Prefix matching
|
|
63
|
+
this.suffixTree = new RaxRadixTree() // Suffix matching
|
|
64
|
+
this.equalsMap = {}
|
|
65
|
+
this.prefixRouteCount = 0;
|
|
66
|
+
this.suffixRouteCount = 0;
|
|
67
|
+
this.equalsRouteCount = 0;
|
|
68
|
+
this.matchFunc = matchFunc
|
|
69
|
+
this._initRoutes()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_initRoute(route, routePath) {
|
|
73
|
+
const pathInfo = parsePath(routePath)
|
|
74
|
+
if (!route.methods) {
|
|
75
|
+
route.methods = ['ALL']
|
|
76
|
+
}
|
|
77
|
+
const treePath = pathInfo.treePath
|
|
78
|
+
const path = pathInfo.path
|
|
79
|
+
delete pathInfo.path
|
|
80
|
+
|
|
81
|
+
if (pathInfo.matchType == MATCH_TYPE_EQUALS) {
|
|
82
|
+
const routeInfo = Object.assign({pathInfo, path}, route);
|
|
83
|
+
let index = this.equalsMap[treePath]
|
|
84
|
+
if (index === undefined) {
|
|
85
|
+
this.routes.push([routeInfo]);
|
|
86
|
+
index = this.routes.length - 1;
|
|
87
|
+
this.equalsMap[treePath] = index;
|
|
88
|
+
} else {
|
|
89
|
+
this.routes[index].push(routeInfo);
|
|
90
|
+
}
|
|
91
|
+
this.equalsRouteCount++;
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pathInfo.pattern = new RegExp(pathInfo.pathPattern);
|
|
96
|
+
const routeInfo = Object.assign({pathInfo, path}, route);
|
|
97
|
+
const tree = pathInfo.matchType === MATCH_TYPE_SUFFIX ? this.suffixTree : this.prefixTree;
|
|
98
|
+
// console.log("pathInfo.matchType: %s, tree: %s", pathInfo.matchType, treePath)
|
|
99
|
+
let index = tree.find(treePath)
|
|
100
|
+
if (index === null) {
|
|
101
|
+
this.routes.push([routeInfo]);
|
|
102
|
+
index = this.routes.length - 1;
|
|
103
|
+
tree.insert(treePath, index)
|
|
104
|
+
} else {
|
|
105
|
+
assert(index >= 0 && index < this.routes.length);
|
|
106
|
+
this.routes[index].push(routeInfo);
|
|
107
|
+
}
|
|
108
|
+
if (pathInfo.matchType === MATCH_TYPE_PREFIX) {
|
|
109
|
+
this.prefixRouteCount++;
|
|
110
|
+
} else if (pathInfo.matchType === MATCH_TYPE_SUFFIX) {
|
|
111
|
+
this.suffixRouteCount++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_initRoutes() {
|
|
116
|
+
for (let route of this.originRoutes) {
|
|
117
|
+
route = Object.assign({}, route);
|
|
118
|
+
const routePaths = route.paths;
|
|
119
|
+
delete route.paths;
|
|
120
|
+
for(let routePath of routePaths) {
|
|
121
|
+
this._initRoute(route, routePath)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._sortAllRoutes();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_sortAllRoutes() {
|
|
129
|
+
for (let routeArray of this.routes) {
|
|
130
|
+
if (!routeArray) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
routeArray.sort((a, b) => {
|
|
134
|
+
const lengthDiff = b.path.length - a.path.length;
|
|
135
|
+
if (lengthDiff !== 0) {
|
|
136
|
+
return lengthDiff;
|
|
137
|
+
}
|
|
138
|
+
// Compare methods only when both a and b have exactly one method
|
|
139
|
+
if (a.methods.length === 1 && b.methods.length === 1) {
|
|
140
|
+
if (a.methods[0] === 'ALL' && b.methods[0] !== 'ALL') {
|
|
141
|
+
return 1; // a should be placed after
|
|
142
|
+
} else if (a.methods[0] !== 'ALL' && b.methods[0] === 'ALL') {
|
|
143
|
+
return -1; // b should be placed after
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Compare hosts (when hosts length is 1)
|
|
148
|
+
if (a.hosts && a.hosts.length === 1 && b.hosts && b.hosts.length === 1) {
|
|
149
|
+
const aIsWildcard = a.hosts[0].startsWith('*');
|
|
150
|
+
const bIsWildcard = b.hosts[0].startsWith('*');
|
|
151
|
+
if (aIsWildcard && !bIsWildcard) {
|
|
152
|
+
return 1; // a is a wildcard domain, should be placed after
|
|
153
|
+
} else if (!aIsWildcard && bIsWildcard) {
|
|
154
|
+
return -1; // b is a wildcard domain, should be placed after
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If both are 'ALL' or neither is 'ALL', consider them equal
|
|
159
|
+
return 0;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
_routeSimpleMatch(routeInfo, reqPath, method) {
|
|
164
|
+
const { treePath, pattern, matchType } = routeInfo.pathInfo;
|
|
165
|
+
const routeMethods = routeInfo.methods;
|
|
166
|
+
// Check method matching
|
|
167
|
+
if (!routeMethods.includes('ALL') && !routeMethods.includes(method)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Path matching logic
|
|
172
|
+
if (matchType === MATCH_TYPE_EQUALS) {
|
|
173
|
+
return reqPath === routeInfo.path;
|
|
174
|
+
} else if (matchType === MATCH_TYPE_SUFFIX) {
|
|
175
|
+
if (!reqPath.startsWith(treePath)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return pattern.test(reqPath);
|
|
179
|
+
} else if (matchType === MATCH_TYPE_PREFIX) {
|
|
180
|
+
if (!reqPath.startsWith(treePath)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return pattern.test(reqPath);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If no case is matched, return false
|
|
187
|
+
console.error("No case matched, {matchType: %s, path: %s}", matchType, reqPath)
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find all routes that match the given path and method.
|
|
193
|
+
* @param {string} path - The path to match against.
|
|
194
|
+
* @param {string} [method=''] - The HTTP method to match (optional).
|
|
195
|
+
* @returns {Array} An array of matching routes.
|
|
196
|
+
*/
|
|
197
|
+
findAllRoutes(path, method='') {
|
|
198
|
+
const allRoutes = [];
|
|
199
|
+
|
|
200
|
+
// Hash lookup logic
|
|
201
|
+
if (this.equalsRouteCount > 0) {
|
|
202
|
+
const hashIndex = this.equalsMap[path];
|
|
203
|
+
if (hashIndex !== undefined) {
|
|
204
|
+
const exactMatchRoutes = this.routes[hashIndex];
|
|
205
|
+
for (const route of exactMatchRoutes) {
|
|
206
|
+
if (this._routeSimpleMatch(route, path, method)) {
|
|
207
|
+
allRoutes.push(route);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Suffix tree lookup logic
|
|
214
|
+
if (this.suffixRouteCount > 0) {
|
|
215
|
+
const reversePath = path.split('').reverse().join('');
|
|
216
|
+
const treeIt = this.suffixTree.search(reversePath);
|
|
217
|
+
if (treeIt) {
|
|
218
|
+
let idx;
|
|
219
|
+
while ((idx = treeIt.up()) !== -1) {
|
|
220
|
+
if (idx >= this.routes.length) {
|
|
221
|
+
console.error("Index out of range");
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
const routesArr = this.routes[idx];
|
|
225
|
+
for (const route of routesArr) {
|
|
226
|
+
if (this._routeSimpleMatch(route, reversePath, method)) {
|
|
227
|
+
allRoutes.push(route);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Prefix tree lookup logic
|
|
235
|
+
if (this.prefixRouteCount > 0) {
|
|
236
|
+
const treeIt = this.prefixTree.search(path);
|
|
237
|
+
if (treeIt) {
|
|
238
|
+
let idx;
|
|
239
|
+
while ((idx = treeIt.up()) !== -1) {
|
|
240
|
+
if (idx >= this.routes.length) {
|
|
241
|
+
console.error("Index out of range");
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
const routesArr = this.routes[idx];
|
|
245
|
+
for (const route of routesArr) {
|
|
246
|
+
if (this._routeSimpleMatch(route, path, method)) {
|
|
247
|
+
allRoutes.push(route);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return allRoutes;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Find the first matching route for the given request.
|
|
259
|
+
* @param {Object} req - The request object containing path and method.
|
|
260
|
+
* @returns {Object|null} The first matching route, or null if no match is found.
|
|
261
|
+
*/
|
|
262
|
+
findRoute(req) {
|
|
263
|
+
const allRoutes = this.findAllRoutes(req.path, req.method, false);
|
|
264
|
+
if (allRoutes.length === 0) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (!this.matchFunc) {
|
|
268
|
+
return allRoutes[0];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Use matchFunc to test for a match
|
|
272
|
+
for (const route of allRoutes) {
|
|
273
|
+
const matched = this.matchFunc(route, req)
|
|
274
|
+
if (matched) {
|
|
275
|
+
return route;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
exports.parsePath = parsePath;
|
|
284
|
+
exports.RadixTree = RadixTree
|
package/src/radix_tree.c
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#include <stdlib.h>
|
|
2
|
+
#include <string.h>
|
|
3
|
+
#include <netinet/in.h>
|
|
4
|
+
#include <arpa/inet.h>
|
|
5
|
+
#include "radix_tree.h"
|
|
6
|
+
#include "rax/rax.h"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
radix_tree* raxtree_new()
|
|
10
|
+
{
|
|
11
|
+
return (radix_tree *)raxNew();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
int raxtree_remove(radix_tree* t, rt_str_t *buf)
|
|
15
|
+
{
|
|
16
|
+
if (t == NULL) {
|
|
17
|
+
return -1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (buf == NULL || buf->buf == NULL) {
|
|
21
|
+
return -2;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return raxRemove((rax *)t, buf->buf, buf->len, NULL);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
int raxtree_destroy(radix_tree* t)
|
|
28
|
+
{
|
|
29
|
+
if (t == NULL) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
raxFree(t);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
int raxtree_insert(radix_tree* t, const rt_str_t *buf, int idx)
|
|
39
|
+
{
|
|
40
|
+
void *data = (void *)(intptr_t)idx;
|
|
41
|
+
|
|
42
|
+
if (t == NULL) {
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (buf == NULL || buf->buf == NULL) {
|
|
47
|
+
return -2;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return raxInsert((rax *)t, buf->buf, buf->len, data, NULL);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
void* raxtree_find(radix_tree* t, const rt_str_t *buf)
|
|
55
|
+
{
|
|
56
|
+
if (t == NULL) {
|
|
57
|
+
return NULL;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (buf == NULL || buf->buf == NULL) {
|
|
61
|
+
return NULL;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void *res = raxFind((rax *)t, buf->buf, buf->len);
|
|
65
|
+
if (res == raxNotFound) {
|
|
66
|
+
return NULL;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return res;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
radix_iterator* raxtree_new_it(radix_tree* t)
|
|
74
|
+
{
|
|
75
|
+
raxIterator *it = malloc(sizeof(raxIterator));
|
|
76
|
+
if (it == NULL) {
|
|
77
|
+
return NULL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
raxStart(it, (rax *)t);
|
|
81
|
+
return (radix_iterator *)it;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
void* raxtree_search(radix_tree* t, radix_iterator* it, const rt_str_t *buf)
|
|
86
|
+
{
|
|
87
|
+
raxIterator *iter = (raxIterator *)it;
|
|
88
|
+
if (it == NULL) {
|
|
89
|
+
return NULL;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
raxSeek(iter, "<=", buf->buf, buf->len);
|
|
93
|
+
return (void *)iter;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
int raxtree_next(radix_iterator* it, const rt_str_t *buf)
|
|
98
|
+
{
|
|
99
|
+
raxIterator *iter = it;
|
|
100
|
+
|
|
101
|
+
int res = raxNext(iter);
|
|
102
|
+
if (!res) {
|
|
103
|
+
return -1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (iter->key_len > buf->len ||
|
|
107
|
+
memcmp(buf->buf, iter->key, iter->key_len) != 0) {
|
|
108
|
+
return -1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (int)(intptr_t)iter->data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
int raxtree_prev(radix_iterator* it, const rt_str_t *buf)
|
|
116
|
+
{
|
|
117
|
+
raxIterator *iter = it;
|
|
118
|
+
int res;
|
|
119
|
+
|
|
120
|
+
while (1) {
|
|
121
|
+
res = raxPrev(iter);
|
|
122
|
+
if (!res) {
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (iter->key_len > buf->len ||
|
|
127
|
+
memcmp(buf->buf, iter->key, iter->key_len) != 0) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (int)(intptr_t)iter->data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
int raxtree_up(radix_iterator* it, const rt_str_t *buf)
|
|
139
|
+
{
|
|
140
|
+
raxIterator *iter = it;
|
|
141
|
+
int res;
|
|
142
|
+
|
|
143
|
+
while (1) {
|
|
144
|
+
res = raxUp(iter);
|
|
145
|
+
if (!res) {
|
|
146
|
+
return -1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (iter->key_len > buf->len ||
|
|
150
|
+
memcmp(buf->buf, iter->key, iter->key_len) != 0) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (int)(intptr_t)iter->data;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
int raxtree_stop(radix_iterator* it)
|
|
162
|
+
{
|
|
163
|
+
if (!it) {
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
raxStop(it);
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
package/src/radix_tree.h
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#ifndef __RAX_TREE_H
|
|
2
|
+
#define __RAX_TREE_H
|
|
3
|
+
|
|
4
|
+
#ifdef __cplusplus
|
|
5
|
+
extern "C" {
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
#include <stdint.h>
|
|
9
|
+
#include <stdio.h>
|
|
10
|
+
#include <ctype.h>
|
|
11
|
+
#include "rax/rax.h"
|
|
12
|
+
|
|
13
|
+
typedef rax radix_tree;
|
|
14
|
+
typedef raxIterator radix_iterator;
|
|
15
|
+
|
|
16
|
+
typedef struct {
|
|
17
|
+
size_t len;
|
|
18
|
+
unsigned char* buf;
|
|
19
|
+
} rt_str_t;
|
|
20
|
+
|
|
21
|
+
radix_tree *raxtree_new();
|
|
22
|
+
int raxtree_destroy(radix_tree* t);
|
|
23
|
+
int raxtree_insert(radix_tree* t, const rt_str_t *buf, int idx);
|
|
24
|
+
|
|
25
|
+
void *raxtree_find(radix_tree* t, const rt_str_t *buf);
|
|
26
|
+
void *raxtree_search(radix_tree* t, radix_iterator* it, const rt_str_t *buf);
|
|
27
|
+
|
|
28
|
+
radix_iterator* raxtree_new_it(radix_tree* t);
|
|
29
|
+
int raxtree_prev(radix_iterator* it, const rt_str_t *buf);
|
|
30
|
+
int raxtree_next(radix_iterator* it, const rt_str_t *buf);
|
|
31
|
+
int raxtree_up(radix_iterator* it, const rt_str_t *buf);
|
|
32
|
+
int raxtree_stop(radix_iterator* it);
|
|
33
|
+
int raxtree_remove(radix_tree* t, rt_str_t *buf);
|
|
34
|
+
|
|
35
|
+
#ifdef __cplusplus
|
|
36
|
+
}
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
#endif
|
package/src/rax/COPYING
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2017, Salvatore Sanfilippo <antirez@gmail.com>
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
8
|
+
|
|
9
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/src/rax/Makefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
DEBUG?= -g -ggdb
|
|
2
|
+
CFLAGS?= -O2 -Wall -W -std=c99
|
|
3
|
+
LDFLAGS= -lm
|
|
4
|
+
|
|
5
|
+
# Uncomment the following two lines for coverage testing
|
|
6
|
+
#
|
|
7
|
+
# CFLAGS+=-fprofile-arcs -ftest-coverage
|
|
8
|
+
# LDFLAGS+=-lgcov
|
|
9
|
+
|
|
10
|
+
all: rax-test rax-oom-test
|
|
11
|
+
|
|
12
|
+
rax.o: rax.h
|
|
13
|
+
rax-test.o: rax.h
|
|
14
|
+
rax-oom-test.o: rax.h
|
|
15
|
+
|
|
16
|
+
rax-test: rax-test.o rax.o rc4rand.o crc16.o
|
|
17
|
+
$(CC) -o $@ $^ $(LDFLAGS) $(DEBUG)
|
|
18
|
+
|
|
19
|
+
rax-oom-test: rax-oom-test.o rax.o
|
|
20
|
+
$(CC) -o $@ $^ $(LDFLAGS) $(DEBUG)
|
|
21
|
+
|
|
22
|
+
.c.o:
|
|
23
|
+
$(CC) -c $(CFLAGS) $(DEBUG) $<
|
|
24
|
+
|
|
25
|
+
clean:
|
|
26
|
+
rm -f rax-test rax-oom-test *.gcda *.gcov *.gcno *.o
|