File: Tree.xs

package info (click to toggle)
libmaxmind-db-writer-perl 0.300003-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,336 kB
  • sloc: ansic: 3,059; perl: 2,895; makefile: 5; sh: 4
file content (320 lines) | stat: -rw-r--r-- 9,179 bytes parent folder | download | duplicates (3)
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
251
252
253
254
255
256
257
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
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
/* *INDENT-ON* */
#ifdef __cplusplus
extern "C" {
#endif

#include "tree.h"

#ifdef __cplusplus
}
#endif

typedef struct perl_iterator_args_s {
    SV *empty_method;
    SV *node_method;
    SV *data_method;
    SV *receiver;
} perl_iterator_args_s;

MMDBW_tree_s *tree_from_self(SV *self) {
    /* This is a bit wrong since we're looking in the $self hash
       rather than calling a method. I couldn't get method calling
       to work. */
    return *(MMDBW_tree_s **)SvPV_nolen(
        *(hv_fetchs((HV *)SvRV(self), "_tree", 0)));
}

void call_iteration_method(MMDBW_tree_s *tree,
                           perl_iterator_args_s *args,
                           SV *method,
                           const uint64_t node_number,
                           MMDBW_record_s *record,
                           const uint128_t node_ip_num,
                           const uint8_t node_prefix_length,
                           const uint128_t record_ip_num,
                           const uint8_t record_prefix_length,
                           const bool is_right) {
    dSP;

    ENTER;
    SAVETMPS;

    int stack_size = MMDBW_RECORD_TYPE_EMPTY == record->type ||
                             MMDBW_RECORD_TYPE_FIXED_EMPTY == record->type
                         ? 7
                         : 8;

    PUSHMARK(SP);
    EXTEND(SP, stack_size);
    PUSHs((SV *)args->receiver);
    mPUSHs(newSVu64(node_number));
    mPUSHi((int)is_right);
    mPUSHs(newSVu128(node_ip_num));
    mPUSHi(node_prefix_length);
    mPUSHs(newSVu128(record_ip_num));
    mPUSHi(record_prefix_length);
    if (MMDBW_RECORD_TYPE_DATA == record->type) {
        mPUSHs(newSVsv(data_for_key(tree, record->value.key)));
    } else if (MMDBW_RECORD_TYPE_NODE == record->type ||
               MMDBW_RECORD_TYPE_FIXED_NODE == record->type ||
               MMDBW_RECORD_TYPE_ALIAS == record->type) {
        mPUSHi(record->value.node->number);
    }
    PUTBACK;

    int count = call_sv(method, G_VOID);

    SPAGAIN;

    if (count != 0) {
        croak("Expected no items back from ->%s() call", SvPV_nolen(method));
    }

    PUTBACK;
    FREETMPS;
    LEAVE;

    return;
}

SV *method_for_record_type(perl_iterator_args_s *args,
                           const MMDBW_record_type record_type) {
    switch (record_type) {
        case MMDBW_RECORD_TYPE_EMPTY:
        case MMDBW_RECORD_TYPE_FIXED_EMPTY:
            return args->empty_method;
            break;
        case MMDBW_RECORD_TYPE_DATA:
            return args->data_method;
            break;
        case MMDBW_RECORD_TYPE_NODE:
        case MMDBW_RECORD_TYPE_FIXED_NODE:
        case MMDBW_RECORD_TYPE_ALIAS:
            return args->node_method;
            break;
    }

    // This croak is probably okay. It should not happen unless we're adding a
    // new record type and missed this spot.
    croak("unexpected record type");
    return NULL;
}

void call_perl_object(MMDBW_tree_s *tree,
                      MMDBW_node_s *node,
                      const uint128_t node_ip_num,
                      const uint8_t node_prefix_length,
                      void *void_args) {
    perl_iterator_args_s *args = (perl_iterator_args_s *)void_args;

    SV *left_method = method_for_record_type(args, node->left_record.type);

    if (NULL != left_method) {
        call_iteration_method(tree,
                              args,
                              left_method,
                              node->number,
                              &(node->left_record),
                              node_ip_num,
                              node_prefix_length,
                              node_ip_num,
                              node_prefix_length + 1,
                              false);
    }

    SV *right_method = method_for_record_type(args, node->right_record.type);
    if (NULL != right_method) {
        call_iteration_method(
            tree,
            args,
            right_method,
            node->number,
            &(node->right_record),
            node_ip_num,
            node_prefix_length,
            flip_network_bit(tree, node_ip_num, node_prefix_length),
            node_prefix_length + 1,
            true);
    }
    return;
}

/* It'd be nice to return the CV instead but there's no exposed API for
 * calling a CV directly. */
SV *maybe_method(HV *package, const char *const method) {
    GV *gv = gv_fetchmethod_autoload(package, method, 1);
    if (NULL != gv) {
        CV *cv = GvCV(gv);
        if (NULL != cv) {
            return newRV_noinc((SV *)cv);
        }
    }

    return NULL;
}

// clang-format off
/* XXX - it'd be nice to find a way to get the tree from the XS code so we
 * don't have to pass it in all over place - it'd also let us remove at least
 * a few shim methods on the Perl code. */

MODULE = MaxMind::DB::Writer::Tree    PACKAGE = MaxMind::DB::Writer::Tree

#include <stdint.h>

BOOT:
    PERL_MATH_INT128_LOAD_OR_CROAK;

MMDBW_tree_s *
_create_tree(ip_version, record_size, merge_strategy, alias_ipv6, remove_reserved_networks)
    uint8_t ip_version;
    uint8_t record_size;
    MMDBW_merge_strategy merge_strategy;
    bool alias_ipv6;
    bool remove_reserved_networks;

    CODE:
        RETVAL = new_tree(ip_version, record_size, merge_strategy, alias_ipv6, remove_reserved_networks);

    OUTPUT:
        RETVAL

void
_insert_network(self, ip_address, prefix_length, key, data, merge_strategy)
    SV *self;
    char *ip_address;
    uint8_t prefix_length;
    SV *key;
    SV *data;
    MMDBW_merge_strategy merge_strategy;

    CODE:
        MMDBW_tree_s *tree = tree_from_self(self);
        insert_network(tree, ip_address, prefix_length, key, data, merge_strategy);

void
_insert_range(self, start_ip_address, end_ip_address, key, data, merge_strategy)
    SV *self;
    char *start_ip_address;
    char *end_ip_address;
    SV *key;
    SV *data;
    MMDBW_merge_strategy merge_strategy;

    CODE:
        insert_range(tree_from_self(self), start_ip_address, end_ip_address, key, data, merge_strategy);

void
_remove_network(self, ip_address, prefix_length)
    SV *self;
    char *ip_address;
    uint8_t prefix_length;

    CODE:
        remove_network(tree_from_self(self), ip_address, prefix_length);

void
_write_search_tree(self, output, root_data_type, serializer)
    SV *self;
    SV *output;
    SV *root_data_type;
    SV *serializer;

    CODE:
        write_search_tree(tree_from_self(self), output, root_data_type, serializer);

uint32_t
node_count(self)
    SV * self;

    CODE:
        MMDBW_tree_s *tree = tree_from_self(self);
        assign_node_numbers(tree);
        if (tree->node_count > max_record_value(tree)) {
            croak("Node count of %u exceeds record size limit of %u bits",
                tree->node_count, tree->record_size);
        }
        RETVAL = tree->node_count;

    OUTPUT:
        RETVAL

void
iterate(self, object)
    SV *self;
    SV *object;

    CODE:
        MMDBW_tree_s *tree = tree_from_self(self);
        assign_node_numbers(tree);
        HV *package;
        /* It's a blessed object */
        if (sv_isobject(object)) {
            package = SvSTASH(SvRV(object));
        /* It's a package name */
        } else if (SvPOK(object) && !SvROK(object)) {
            package = gv_stashsv(object, 0);
        } else {
            croak("The argument passed to iterate (%s) is not an object or class name", SvPV_nolen(object));
        }

        perl_iterator_args_s args = {
            .empty_method = maybe_method(package, "process_empty_record"),
            .node_method = maybe_method(package, "process_node_record"),
            .data_method = maybe_method(package, "process_data_record"),
            .receiver = object
        };
        if (!(NULL != args.empty_method
              || NULL != args.node_method
              || NULL != args.data_method)) {

            croak("The object or class passed to iterate must implement "
                  "at least one method of process_empty_record, "
                  "process_node_record, or process_data_record");
        }

        start_iteration(tree, true, (void *)&args, &call_perl_object);

SV *
lookup_ip_address(self, address)
    SV *self;
    char *address;

    CODE:
        RETVAL = lookup_ip_address(tree_from_self(self), address);

    OUTPUT:
        RETVAL

void
_freeze_tree(self, filename, frozen_params, frozen_params_size)
    SV *self;
    char *filename;
    char *frozen_params;
    int frozen_params_size;

    CODE:
        freeze_tree(tree_from_self(self), filename, frozen_params, frozen_params_size);

MMDBW_tree_s *
_thaw_tree(filename, initial_offset, ip_version, record_size, merge_strategy, alias_ipv6, remove_reserved_networks)
    char *filename;
    int initial_offset;
    int ip_version;
    int record_size;
    MMDBW_merge_strategy merge_strategy;
    bool alias_ipv6;
    bool remove_reserved_networks;

    CODE:
        RETVAL = thaw_tree(filename, initial_offset, ip_version, record_size, merge_strategy, alias_ipv6, remove_reserved_networks);

    OUTPUT:
        RETVAL

void
_free_tree(self)
    SV *self;

    CODE:
        free_tree(tree_from_self(self));