File Coverage

MMDB.xs
Criterion Covered Total %
statement 211 239 88.2
branch 55 128 42.9
condition n/a
subroutine n/a
pod n/a
total 266 367 72.4


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