File Coverage

MMDB.xs
Criterion Covered Total %
statement 216 245 88.1
branch 63 140 45.0
condition n/a
subroutine n/a
pod n/a
total 279 385 72.4


line stmt bran cond sub pod time code
1             /*
2             * Read MaxMind DB files
3             *
4             * Copyright (C) 2022 Andreas Vögele
5             *
6             * This module is free software; you can redistribute it and/or modify it
7             * under the same terms as Perl itself.
8             */
9              
10             /* SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later */
11              
12             #define PERL_NO_GET_CONTEXT
13             #include "EXTERN.h"
14             #include "perl.h"
15             #include "XSUB.h"
16              
17             #include
18              
19             #ifdef MULTIPLICITY
20             #define storeTHX(var) (var) = aTHX
21             #define dTHXfield(var) tTHX var;
22             #else
23             #define storeTHX(var) dNOOP
24             #define dTHXfield(var)
25             #endif
26              
27             typedef struct ip_geolocation_mmdb {
28             MMDB_s mmdb;
29             SV *file;
30             SV *selfrv;
31             dTHXfield(perl)
32             } *IP__Geolocation__MMDB;
33              
34             typedef struct {
35             IP__Geolocation__MMDB self;
36             SV *data_callback;
37             SV *node_callback;
38             int max_depth;
39             } iterate_data;
40              
41             static void
42 1           init_iterate_data(iterate_data *data, IP__Geolocation__MMDB self,
43             SV *data_callback, SV *node_callback)
44             {
45 1           data->self = self;
46 1           data->data_callback = data_callback;
47 1           data->node_callback = node_callback;
48 1 50         data->max_depth = (6 == self->mmdb.metadata.ip_version) ? 128 : 32;
49 1           }
50              
51             static SV *
52 14           to_bigint(IP__Geolocation__MMDB self, const char *bytes, size_t size)
53             {
54             dTHXa(self->perl);
55              
56 14           dSP;
57             int count;
58             SV *err_tmp;
59             SV *retval;
60              
61 14           ENTER;
62 14           SAVETMPS;
63              
64 14 50         PUSHMARK(SP);
65 14 50         EXTEND(SP, 2);
66 14           mPUSHs(newRV_inc(self->selfrv));
67 14           mPUSHp(bytes, size);
68 14           PUTBACK;
69 14           count = call_method("_to_bigint", G_SCALAR | G_EVAL);
70 14           SPAGAIN;
71 14 50         err_tmp = ERRSV;
72 14 50         if (SvTRUE(err_tmp)) {
    50          
    0          
    50          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
73 0           (void) POPs;
74 0           retval = newSVpvn(bytes, size);
75             }
76             else {
77 14 50         if (1 == count) {
78 14           retval = newSVsv(POPs);
79             }
80             else {
81 0           retval = newSVpvn(bytes, size);
82             }
83             }
84 14           PUTBACK;
85              
86 14 50         FREETMPS;
87 14           LEAVE;
88              
89 14           return retval;
90             }
91              
92             typedef struct {
93             char bytes[16];
94             } numeric_ip;
95              
96             static void
97 1           init_numeric_ip(numeric_ip *ipnum)
98             {
99 1           Zero(ipnum, sizeof(*ipnum), char);
100 1           }
101              
102             static void
103 832           numeric_ip_set_bit(numeric_ip *ipnum, int bit)
104             {
105 832           int quot = bit / (8 * sizeof(char));
106 832           int rem = bit % (8 * sizeof(char));
107 832           ipnum->bytes[15 - quot] |= (128 >> (7 - rem));
108 832           }
109              
110             static SV *
111 5           numeric_ip_to_bigint(IP__Geolocation__MMDB self, const numeric_ip *ipnum)
112             {
113 5           return to_bigint(self, ipnum->bytes, sizeof(ipnum->bytes));
114             }
115              
116             #if MMDB_UINT128_IS_BYTE_ARRAY
117             static SV *
118             createSVu128(IP__Geolocation__MMDB self, uint8_t u[16])
119             {
120             char bytes[16];
121             size_t n;
122             for (n = 0; n < 16; ++n) {
123             bytes[n] = (char) u[n];
124             }
125             return to_bigint(self, bytes, sizeof(bytes));
126             }
127             #else
128             static SV *
129 9           createSVu128(IP__Geolocation__MMDB self, mmdb_uint128_t u)
130             {
131             #ifdef WORDS_BIGENDIAN
132             return to_bigint(self, (const char *) &u, sizeof(u));
133             #else
134             char bytes[sizeof(u)];
135             size_t n;
136 153 100         for (n = 0; n < sizeof(u); ++n) {
137 144           bytes[n] = ((const char *) &u)[sizeof(u) - n - 1];
138             }
139 9           return to_bigint(self, bytes, sizeof(bytes));
140             #endif
141             }
142             #endif
143              
144             #if UVSIZE >= 8
145             #define createSVu64(self, u) newSVuv(u)
146             #else
147             static SV *
148             createSVu64(IP__Geolocation__MMDB self, uint64_t u)
149             {
150             #ifdef WORDS_BIGENDIAN
151             return to_bigint(self, (const char *) &u, sizeof(u));
152             #else
153             char bytes[sizeof(u)];
154             size_t n;
155             for (n = 0; n < sizeof(u); ++n) {
156             bytes[n] = ((const char *) &u)[sizeof(u) - n - 1];
157             }
158             return to_bigint(self, bytes, sizeof(bytes));
159             #endif
160             }
161             #endif
162              
163             static MMDB_entry_data_list_s *
164 482           decode_entry_data_list(IP__Geolocation__MMDB self,
165             MMDB_entry_data_list_s *list, SV **sv, int *mmdb_error)
166             {
167             dTHXa(self->perl);
168 482           MMDB_entry_data_s *data = &list->entry_data;
169 482           switch (data->type) {
170             case MMDB_DATA_TYPE_MAP: {
171 101           uint32_t size = data->data_size;
172 101           HV *hv = newHV();
173 101           hv_ksplit(hv, size);
174 534 100         for (list = list->next; size > 0 && NULL != list; size--) {
    50          
175 433 50         if (MMDB_DATA_TYPE_UTF8_STRING != list->entry_data.type) {
176 0           *mmdb_error = MMDB_INVALID_DATA_ERROR;
177 0           return NULL;
178             }
179 433           const char *key = list->entry_data.utf8_string;
180 433           uint32_t key_size = list->entry_data.data_size;
181 433           list = list->next;
182 433 50         if (NULL == list) {
183 0           *mmdb_error = MMDB_INVALID_DATA_ERROR;
184 0           return NULL;
185             }
186 433           SV *val = &PL_sv_undef;
187 433           list = decode_entry_data_list(self, list, &val, mmdb_error);
188 433 50         if (MMDB_SUCCESS != *mmdb_error) {
189 0           return NULL;
190             }
191 433           (void) hv_store(hv, key, key_size, val, 0);
192             }
193 101           *sv = newRV_noinc((SV *) hv);
194             }
195 101           break;
196              
197             case MMDB_DATA_TYPE_ARRAY: {
198 19           uint32_t size = data->data_size;
199 19           AV *av = newAV();
200 19           av_extend(av, size);
201 58 100         for (list = list->next; size > 0 && NULL != list; size--) {
    50          
202 39           SV *val = &PL_sv_undef;
203 39           list = decode_entry_data_list(self, list, &val, mmdb_error);
204 39 50         if (MMDB_SUCCESS != *mmdb_error) {
205 0           return NULL;
206             }
207 39           av_push(av, val);
208             }
209 19           *sv = newRV_noinc((SV *) av);
210             }
211 19           break;
212              
213             case MMDB_DATA_TYPE_UTF8_STRING:
214 158           *sv = newSVpvn_utf8(data->utf8_string, data->data_size, 1);
215 158           list = list->next;
216 158           break;
217              
218             case MMDB_DATA_TYPE_BYTES:
219 9           *sv = newSVpvn((const char *) data->bytes, data->data_size);
220 9           list = list->next;
221 9           break;
222              
223             case MMDB_DATA_TYPE_DOUBLE:
224 27           *sv = newSVnv(data->double_value);
225 27           list = list->next;
226 27           break;
227              
228             case MMDB_DATA_TYPE_FLOAT:
229 9           *sv = newSVnv(data->float_value);
230 9           list = list->next;
231 9           break;
232              
233             case MMDB_DATA_TYPE_UINT16:
234 49           *sv = newSVuv(data->uint16);
235 49           list = list->next;
236 49           break;
237              
238             case MMDB_DATA_TYPE_UINT32:
239 37           *sv = newSVuv(data->uint32);
240 37           list = list->next;
241 37           break;
242              
243             case MMDB_DATA_TYPE_BOOLEAN:
244 18           *sv = newSViv(data->boolean);
245 18           list = list->next;
246 18           break;
247              
248             case MMDB_DATA_TYPE_UINT64:
249 10           *sv = createSVu64(self, data->uint64);
250 10           list = list->next;
251 10           break;
252              
253             case MMDB_DATA_TYPE_UINT128:
254 9           *sv = createSVu128(self, data->uint128);
255 9           list = list->next;
256 9           break;
257              
258             case MMDB_DATA_TYPE_INT32:
259 36           *sv = newSViv(data->int32);
260 36           list = list->next;
261 36           break;
262              
263             default:
264 0           *mmdb_error = MMDB_INVALID_DATA_ERROR;
265 0           return NULL;
266             }
267              
268 482           *mmdb_error = MMDB_SUCCESS;
269 482           return list;
270             }
271              
272             static void
273 832           call_node_callback(iterate_data *data, uint32_t node_num,
274             MMDB_search_node_s *node)
275             {
276 832           IP__Geolocation__MMDB self = data->self;
277             dTHXa(self->perl);
278              
279 832 50         if (!SvOK(data->node_callback)) {
    0          
    0          
280 0           return;
281             }
282              
283 832           dSP;
284 832           SV *left_record = createSVu64(self, node->left_record);
285 832           SV *right_record = createSVu64(self, node->right_record);
286              
287 832           ENTER;
288 832           SAVETMPS;
289              
290 832 50         PUSHMARK(SP);
291 832 50         EXTEND(SP, 3);
292 832           mPUSHu(node_num);
293 832           mPUSHs(left_record);
294 832           mPUSHs(right_record);
295 832           PUTBACK;
296 832           (void) call_sv(data->node_callback, G_VOID);
297              
298 832 50         FREETMPS;
299 832           LEAVE;
300             }
301              
302             static void
303 5           call_data_callback(iterate_data *data, numeric_ip ipnum, int depth,
304             MMDB_entry_s *record_entry)
305             {
306 5           IP__Geolocation__MMDB self = data->self;
307             dTHXa(self->perl);
308              
309 5 50         if (!SvOK(data->data_callback)) {
    0          
    0          
310 0           return;
311             }
312              
313 5           SV *decoded_entry = &PL_sv_undef;
314 5           MMDB_entry_data_list_s *list = NULL;
315 5           int mmdb_error = MMDB_get_entry_data_list(record_entry, &list);
316 5 50         if (MMDB_SUCCESS == mmdb_error) {
317 5           (void) decode_entry_data_list(self, list, &decoded_entry,
318             &mmdb_error);
319             }
320 5           MMDB_free_entry_data_list(list);
321 5 50         if (MMDB_SUCCESS != mmdb_error) {
322 0           const char *error = MMDB_strerror(mmdb_error);
323 0           croak("Entry data error looking at offset %u: %s",
324 0           (unsigned int) record_entry->offset, error);
325             }
326              
327 5           SV *ip = numeric_ip_to_bigint(self, &ipnum);
328              
329 5           dSP;
330              
331 5           ENTER;
332 5           SAVETMPS;
333              
334 5 50         PUSHMARK(SP);
335 5 50         EXTEND(SP, 3);
336 5           mPUSHs(ip);
337 5           mPUSHi(depth);
338 5           mPUSHs(decoded_entry);
339 5           PUTBACK;
340              
341 5           (void) call_sv(data->data_callback, G_VOID);
342              
343 5 50         FREETMPS;
344 5           LEAVE;
345             }
346              
347             static void iterate_search_nodes(iterate_data *, uint32_t, numeric_ip, int);
348              
349             static void
350 1664           iterate_record_entry(iterate_data *data, numeric_ip ipnum, int depth,
351             uint64_t record, uint8_t record_type,
352             MMDB_entry_s *record_entry)
353             {
354 1664           switch (record_type) {
355             case MMDB_RECORD_TYPE_INVALID:
356 0           croak("%s", "Invalid record when reading node");
357             break;
358             case MMDB_RECORD_TYPE_SEARCH_NODE:
359 831           iterate_search_nodes(data, (uint32_t) record, ipnum, depth + 1);
360 831           break;
361             case MMDB_RECORD_TYPE_EMPTY:
362             /* Empty branches are ignored. */
363 828           break;
364             case MMDB_RECORD_TYPE_DATA:
365 5           call_data_callback(data, ipnum, depth, record_entry);
366 5           break;
367             default:
368 0           croak("Unknown record type: %u", (unsigned int) record_type);
369             break;
370             }
371 1664           }
372              
373             static void
374 832           iterate_search_nodes(iterate_data *data, uint32_t node_num, numeric_ip ipnum,
375             int depth)
376             {
377             MMDB_search_node_s node;
378 832           int mmdb_error = MMDB_read_node(&data->self->mmdb, node_num, &node);
379 832 50         if (MMDB_SUCCESS != mmdb_error) {
380 0           const char *error = MMDB_strerror(mmdb_error);
381 0           croak("Error reading node %u: %s", (unsigned int) node_num, error);
382             }
383              
384 832 50         if (depth > data->max_depth) {
385 0           croak("Invalid depth when reading node %u: %d", (unsigned int)
386             node_num, depth);
387             }
388              
389 832           call_node_callback(data, node_num, &node);
390              
391 832           iterate_record_entry(data, ipnum, depth, node.left_record,
392 832           node.left_record_type, &node.left_record_entry);
393              
394 832           numeric_ip_set_bit(&ipnum, data->max_depth - depth);
395              
396 832           iterate_record_entry(data, ipnum, depth, node.right_record,
397 832           node.right_record_type, &node.right_record_entry);
398 832           }
399              
400             MODULE = IP::Geolocation::MMDB PACKAGE = IP::Geolocation::MMDB
401              
402             PROTOTYPES: DISABLE
403              
404             TYPEMAP: <
405             IP::Geolocation::MMDB T_PTROBJ
406             HERE
407              
408             SV *
409             new(klass, ...)
410             SV *klass
411             INIT:
412             IP__Geolocation__MMDB self;
413 3           SV *file = NULL;
414 3           U32 flags = 0;
415             I32 i;
416             const char *key;
417             SV *value;
418             const char *filename;
419             int mmdb_error;
420             const char *error;
421             CODE:
422 3 50         if ((items - 1) % 2 != 0) {
423 0           warn("Odd-length list passed to %" SVf " constructor", SVfARG(klass));
424             }
425              
426 5 100         for (i = 1; i < items; i += 2) {
427 2 50         key = SvPV_nolen_const(ST(i));
428 2           value = ST(i + 1);
429 2 50         if (strEQ(key, "file")) {
430 2           file = value;
431             }
432             }
433              
434 3 100         if (NULL == file) {
435 1           croak("The \"file\" parameter is mandatory");
436             }
437              
438 2 50         filename = SvPVbyte_nolen(file);
439              
440 2           Newxz(self, 1, struct ip_geolocation_mmdb);
441             storeTHX(self->perl);
442 2           self->file = SvREFCNT_inc(file);
443              
444 2           mmdb_error = MMDB_open(filename, flags, &self->mmdb);
445 2 100         if (MMDB_SUCCESS != mmdb_error) {
446 1           Safefree(self);
447 1           error = MMDB_strerror(mmdb_error);
448 1           croak("Error opening database file \"%" SVf "\": %s", SVfARG(file),
449             error);
450             }
451              
452 1           RETVAL = sv_bless(newRV_noinc(newSViv(PTR2IV(self))),
453             gv_stashsv(klass, GV_ADD));
454 1           self->selfrv = SvRV(RETVAL); /* no inc */
455             OUTPUT:
456             RETVAL
457              
458             void
459             DESTROY(self)
460             IP::Geolocation::MMDB self
461             CODE:
462 1           MMDB_close(&self->mmdb);
463 1           SvREFCNT_dec(self->file);
464 1           Safefree(self);
465              
466             SV *
467             record_for_address(self, ...)
468             IP::Geolocation::MMDB self
469             INIT:
470             const char *ip_address;
471             int gai_error, mmdb_error;
472             const char *error;
473             MMDB_lookup_result_s result;
474             MMDB_entry_data_list_s *list;
475             CODE:
476 6           ip_address = NULL;
477 6 50         if (items > 1) {
478 6 50         ip_address = SvPVbyte_nolen(ST(1));
479             }
480 6 50         if (NULL == ip_address || '\0' == *ip_address) {
    50          
481 0           croak("%s", "You must provide an IP address to look up");
482             }
483 6           result =
484 6           MMDB_lookup_string(&self->mmdb, ip_address, &gai_error, &mmdb_error);
485 6 100         if (0 != gai_error) {
486 1           croak("The IP address you provided (%s) is not a valid IPv4 or IPv6 "
487             "address", ip_address);
488             }
489 5 50         if (MMDB_SUCCESS != mmdb_error) {
490 0           error = MMDB_strerror(mmdb_error);
491 0           croak("Error looking up IP address \"%s\": %s", ip_address, error);
492             }
493 5           RETVAL = &PL_sv_undef;
494 5 100         if (result.found_entry) {
495 4           list = NULL;
496 4           mmdb_error = MMDB_get_entry_data_list(&result.entry, &list);
497 4 50         if (MMDB_SUCCESS == mmdb_error) {
498 4           (void) decode_entry_data_list(self, list, &RETVAL, &mmdb_error);
499             }
500 4           MMDB_free_entry_data_list(list);
501 4 50         if (MMDB_SUCCESS != mmdb_error) {
502 0           error = MMDB_strerror(mmdb_error);
503 0           croak("Entry data error looking up \"%s\": %s", ip_address,
504             error);
505             }
506             }
507             OUTPUT:
508             RETVAL
509              
510             void
511             iterate_search_tree(self, ...)
512             IP::Geolocation::MMDB self
513             INIT:
514             SV *data_callback;
515             SV *node_callback;
516             iterate_data data;
517             numeric_ip ipnum;
518             CODE:
519 1           data_callback = &PL_sv_undef;
520 1           node_callback = &PL_sv_undef;
521 1 50         if (items > 1) {
522 1           data_callback = ST(1);
523 1 50         if (items > 2) {
524 1           node_callback = ST(2);
525             }
526             }
527 1           init_iterate_data(&data, self, data_callback, node_callback);
528 1           init_numeric_ip(&ipnum);
529 1           iterate_search_nodes(&data, 0, ipnum, 1);
530              
531             SV *
532             _metadata(self)
533             IP::Geolocation::MMDB self
534             INIT:
535             int mmdb_error;
536             const char *error;
537             MMDB_entry_data_list_s *list;
538             CODE:
539 1           RETVAL = &PL_sv_undef;
540 1           list = NULL;
541 1           mmdb_error = MMDB_get_metadata_as_entry_data_list(&self->mmdb, &list);
542 1 50         if (MMDB_SUCCESS == mmdb_error) {
543 1           (void) decode_entry_data_list(self, list, &RETVAL, &mmdb_error);
544             }
545 1           MMDB_free_entry_data_list(list);
546 1 50         if (MMDB_SUCCESS != mmdb_error) {
547 0           error = MMDB_strerror(mmdb_error);
548 0           croak("Error getting metadata: %s", error);
549             }
550             OUTPUT:
551             RETVAL
552              
553             SV *
554             file(self)
555             IP::Geolocation::MMDB self
556             CODE:
557 1           RETVAL = SvREFCNT_inc(self->file);
558             OUTPUT:
559             RETVAL
560              
561             const char *
562             libmaxminddb_version()
563             CODE:
564 1           RETVAL = MMDB_lib_version();
565             OUTPUT:
566             RETVAL