1 /* *INDENT-ON* */
2 #ifdef __cplusplus
3 extern "C" {
4 #endif
5 
6 #include "EXTERN.h"
7 #include "perl.h"
8 #include "XSUB.h"
9 #define NEED_newRV_noinc
10 #include "ppport.h"
11 #include <sys/socket.h>
12 #include "maxminddb.h"
13 
14 #define MATH_INT64_NATIVE_IF_AVAILABLE
15 #include "perl_math_int64.h"
16 #include "perl_math_int128.h"
17 
18 #ifdef __cplusplus
19 }
20 #endif
21 
22 static void iterate_record_entry(MMDB_s *mmdb, SV *data_callback,
23                                  SV *node_callback, uint32_t node_num,
24                                  mmdb_uint128_t ipnum, int depth,
25                                  int max_depth, uint64_t record,
26                                  uint8_t record_type,
27                                  MMDB_entry_s *record_entry);
28 
decode_bytes(MMDB_entry_data_s * entry_data)29 static SV *decode_bytes(MMDB_entry_data_s *entry_data)
30 {
31     return newSVpvn((char *)entry_data->bytes, entry_data->data_size);
32 }
33 
decode_simple_value(MMDB_entry_data_list_s ** current)34 static SV *decode_simple_value(MMDB_entry_data_list_s **current)
35 {
36     MMDB_entry_data_s entry_data = (*current)->entry_data;
37     switch (entry_data.type) {
38     case MMDB_DATA_TYPE_UTF8_STRING:
39         return newSVpvn_utf8((char *)entry_data.utf8_string, entry_data.data_size, 1);
40     case MMDB_DATA_TYPE_DOUBLE:
41         return newSVnv(entry_data.double_value);
42     case MMDB_DATA_TYPE_BYTES:
43         return decode_bytes(&entry_data);
44     case MMDB_DATA_TYPE_FLOAT:
45         return newSVnv(entry_data.float_value);
46     case MMDB_DATA_TYPE_UINT16:
47         return newSVuv(entry_data.uint16);
48     case MMDB_DATA_TYPE_UINT32:
49         return newSVuv(entry_data.uint32);
50     case MMDB_DATA_TYPE_INT32:
51         return newSViv(entry_data.int32);
52     case MMDB_DATA_TYPE_UINT64:
53         return newSVu64(entry_data.uint64);
54     case MMDB_DATA_TYPE_UINT128:
55         /* We don't handle the case where uint128 is a byte array since even
56          * the pure Perl MaxMind::DB::Reader requires Math::Int128, which in
57          * turn requires GCC 4.4+. Therefore we know that we have an int128
58          * type available if this code is compiling at all. */
59         return newSVu128(entry_data.uint128);
60     case MMDB_DATA_TYPE_BOOLEAN:
61         /* Note to future coders - do not use PL_sv_yes, PL_sv_no, or bool_sv
62          * - these all produce read-only SVs */
63         return newSViv(entry_data.boolean);
64     default:
65         croak(
66             "MaxMind::DB::Reader::XS - error decoding unknown type number %i",
67             entry_data.type
68             );
69     }
70 
71     /* It shouldn't be possible to reach this. */
72     return NULL;
73 }
74 
75 static SV *decode_entry_data_list(MMDB_entry_data_list_s **entry_data_list);
76 
decode_array(MMDB_entry_data_list_s ** current)77 static SV *decode_array(MMDB_entry_data_list_s **current)
78 {
79     int size = (*current)->entry_data.data_size;
80 
81     AV *av = newAV();
82     av_extend(av, size);
83     for (uint i = 0; i < size; i++) {
84         *current = (*current)->next;
85         av_push(av, decode_entry_data_list(current));
86     }
87 
88     return newRV_noinc((SV *)av);
89 }
90 
decode_map(MMDB_entry_data_list_s ** current)91 static SV *decode_map(MMDB_entry_data_list_s **current)
92 {
93     int size = (*current)->entry_data.data_size;
94 
95     HV *hv = newHV();
96     hv_ksplit(hv, size);
97     for (uint i = 0; i < size; i++) {
98         *current = (*current)->next;
99         char *key = (char *)(*current)->entry_data.utf8_string;
100         int key_size = (*current)->entry_data.data_size;
101         *current = (*current)->next;
102         SV *val = decode_entry_data_list(current);
103         (void)hv_store(hv, key, key_size, val, 0);
104     }
105 
106     return newRV_noinc((SV *)hv);
107 }
108 
decode_entry_data_list(MMDB_entry_data_list_s ** current)109 static SV *decode_entry_data_list(MMDB_entry_data_list_s **current)
110 {
111     switch ((*current)->entry_data.type) {
112     case MMDB_DATA_TYPE_MAP:
113         return decode_map(current);
114     case MMDB_DATA_TYPE_ARRAY:
115         return decode_array(current);
116     default:
117         return decode_simple_value(current);
118     }
119 }
120 
decode_and_free_entry_data_list(MMDB_entry_data_list_s * entry_data_list)121 static SV *decode_and_free_entry_data_list(
122     MMDB_entry_data_list_s *entry_data_list)
123 {
124     MMDB_entry_data_list_s *current = entry_data_list;
125     SV *sv = decode_entry_data_list(&current);
126     MMDB_free_entry_data_list(entry_data_list);
127     return sv;
128 }
129 
130 
call_node_callback(SV * node_callback,uint32_t node_num,MMDB_search_node_s * node)131 static void call_node_callback(SV *node_callback, uint32_t node_num,
132                                MMDB_search_node_s *node)
133 {
134     if (!SvOK(node_callback)) {
135         // nothing to do
136         return;
137     }
138 
139     dSP;
140 
141     ENTER;
142     SAVETMPS;
143 
144     PUSHMARK(SP);
145     EXTEND(SP, 3);
146     mPUSHu(node_num);
147     mPUSHs(newSVu64(node->left_record));
148     mPUSHs(newSVu64(node->right_record));
149     PUTBACK;
150 
151     call_sv(node_callback, G_VOID);
152 
153     FREETMPS;
154     LEAVE;
155 
156     return;
157 }
158 
call_data_callback(MMDB_s * mmdb,SV * data_callback,mmdb_uint128_t ipnum,int depth,MMDB_entry_s * record_entry)159 static void call_data_callback(MMDB_s *mmdb, SV *data_callback,
160                                mmdb_uint128_t ipnum, int depth,
161                                MMDB_entry_s *record_entry)
162 {
163 
164     if (!SvOK(data_callback)) {
165         // nothing to do
166         return;
167     }
168 
169     MMDB_entry_data_list_s *entry_data_list;
170     int status = MMDB_get_entry_data_list(record_entry, &entry_data_list);
171     if (MMDB_SUCCESS != status) {
172         const char *error = MMDB_strerror(status);
173         MMDB_free_entry_data_list(entry_data_list);
174         croak(
175             "MaxMind::DB::Reader::XS - Entry data error looking at offset %i: %s",
176             record_entry->offset, error
177             );
178     }
179     SV *decoded_entry = decode_and_free_entry_data_list(entry_data_list);
180 
181     dSP;
182 
183     ENTER;
184     SAVETMPS;
185 
186     PUSHMARK(SP);
187     EXTEND(SP, 3);
188     mPUSHs(newSVu128(ipnum));
189     mPUSHi(depth);
190     mPUSHs(decoded_entry);
191     PUTBACK;
192 
193     call_sv(data_callback, G_VOID);
194 
195     FREETMPS;
196     LEAVE;
197 
198     return;
199 }
200 
iterate_search_nodes(MMDB_s * mmdb,SV * data_callback,SV * node_callback,uint32_t node_num,mmdb_uint128_t ipnum,int depth,int max_depth)201 static void iterate_search_nodes(MMDB_s *mmdb, SV *data_callback,
202                                   SV *node_callback, uint32_t node_num,
203                                   mmdb_uint128_t ipnum,
204                                   int depth,
205                                   int max_depth)
206 {
207 
208     MMDB_search_node_s node;
209     int status = MMDB_read_node(mmdb, node_num, &node);
210     if (MMDB_SUCCESS != status) {
211         const char *error = MMDB_strerror(status);
212         croak(
213             "MaxMind::DB::Reader::XS - Error reading node: %s",
214             error
215             );
216     }
217 
218     call_node_callback(node_callback, node_num, &node);
219 
220     iterate_record_entry(mmdb, data_callback, node_callback, node_num, ipnum,
221                          depth, max_depth, node.left_record,
222                          node.left_record_type,
223                          &node.left_record_entry);
224 
225     ipnum |= ((mmdb_uint128_t)1) << ( max_depth - depth );
226 
227     iterate_record_entry(mmdb, data_callback, node_callback, node_num, ipnum,
228                          depth, max_depth, node.right_record,
229                          node.right_record_type,
230                          &node.right_record_entry);
231 }
232 
iterate_record_entry(MMDB_s * mmdb,SV * data_callback,SV * node_callback,uint32_t node_num,mmdb_uint128_t ipnum,int depth,int max_depth,uint64_t record,uint8_t record_type,MMDB_entry_s * record_entry)233 static void iterate_record_entry(MMDB_s *mmdb, SV *data_callback,
234                                  SV *node_callback, uint32_t node_num,
235                                  mmdb_uint128_t ipnum, int depth,
236                                  int max_depth, uint64_t record,
237                                  uint8_t record_type,
238                                  MMDB_entry_s *record_entry)
239 {
240 
241     switch (record_type) {
242     case MMDB_RECORD_TYPE_INVALID:
243         croak(
244             "MaxMind::DB::Reader::XS - Invalid record when reading node"
245             );
246     case MMDB_RECORD_TYPE_SEARCH_NODE:
247         iterate_search_nodes(mmdb, data_callback, node_callback, record,
248                               ipnum, depth + 1, max_depth);
249         return;
250     case  MMDB_RECORD_TYPE_EMPTY:
251         // We ignore empty branches of the search tree
252         return;
253     case MMDB_RECORD_TYPE_DATA:
254         call_data_callback(mmdb, data_callback, ipnum, depth,
255                            record_entry);
256         return;
257     default:
258         croak("MaxMind::DB::Reader::XS - Unknown record type: %u",
259               record_type);
260     }
261 }
262 
263 /* *INDENT-OFF* */
264 
265 MODULE = MaxMind::DB::Reader::XS    PACKAGE = MaxMind::DB::Reader::XS
266 
267 BOOT:
268      PERL_MATH_INT64_LOAD_OR_CROAK;
269      PERL_MATH_INT128_LOAD_OR_CROAK;
270 
271 MMDB_s *
272 _open_mmdb(self, file, flags)
273     char *file;
274     U32 flags;
275     PREINIT:
276         MMDB_s *mmdb;
277         uint16_t status;
278 
279     CODE:
280         if (file == NULL) {
281             croak("MaxMind::DB::Reader::XS - No file passed to _open_mmdb()\n");
282         }
283         mmdb = (MMDB_s *)malloc(sizeof(MMDB_s));
284         status = MMDB_open(file, flags, mmdb);
285 
286         if (MMDB_SUCCESS != status) {
287             const char *error = MMDB_strerror(status);
288             free(mmdb);
289             croak(
290                 "MaxMind::DB::Reader::XS - Error opening database file \"%s\": %s",
291                 file, error
292                 );
293         }
294 
295         RETVAL = mmdb;
296     OUTPUT:
297         RETVAL
298 
299 void
300 _close_mmdb(self, mmdb)
301         MMDB_s *mmdb;
302     CODE:
303         MMDB_close(mmdb);
304         free(mmdb);
305 
306 SV *
_raw_metadata(self,mmdb)307 _raw_metadata(self, mmdb)
308         MMDB_s *mmdb
309     PREINIT:
310         MMDB_entry_data_list_s *entry_data_list;
311     CODE:
312         int status = MMDB_get_metadata_as_entry_data_list(mmdb, &entry_data_list);
313         if (MMDB_SUCCESS != status) {
314             const char *error = MMDB_strerror(status);
315             MMDB_free_entry_data_list(entry_data_list);
316             croak(
317                 "MaxMind::DB::Reader::XS - Error getting metadata: %s",
318                 error
319                 );
320         }
321 
322         RETVAL = decode_and_free_entry_data_list(entry_data_list);
323     OUTPUT:
324         RETVAL
325 
326 SV *
__data_for_address(self,mmdb,ip_address)327 __data_for_address(self, mmdb, ip_address)
328         MMDB_s *mmdb
329         char *ip_address
330     PREINIT:
331         int gai_status, mmdb_status, get_status;
332         MMDB_lookup_result_s result;
333         MMDB_entry_data_list_s *entry_data_list;
334     CODE:
335         if (!ip_address || *ip_address == '\0') {
336             croak("You must provide an IP address to look up");
337         }
338 
339         result = MMDB_lookup_string(mmdb, ip_address, &gai_status, &mmdb_status);
340         if (0 != gai_status) {
341             croak(
342                 "The IP address you provided (%s) is not a valid IPv4 or IPv6 address",
343                 ip_address);
344         }
345 
346         if (MMDB_SUCCESS != mmdb_status) {
347             const char *mmdb_error = MMDB_strerror(mmdb_status);
348             croak(
349                 "MaxMind::DB::Reader::XS - Error looking up IP address \"%s\": %s",
350                 ip_address, mmdb_error
351                 );
352         }
353 
354         if (result.found_entry) {
355             get_status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
356             if (MMDB_SUCCESS != get_status) {
357                 const char *get_error = MMDB_strerror(get_status);
358                 MMDB_free_entry_data_list(entry_data_list);
359                 croak(
360                     "MaxMind::DB::Reader::XS - Entry data error looking up \"%s\": %s",
361                     ip_address, get_error
362                     );
363             }
364             RETVAL = decode_and_free_entry_data_list(entry_data_list);
365         } else {
366             RETVAL = &PL_sv_undef;
367         }
368     OUTPUT:
369         RETVAL
370 
371 void
372 _iterate_search_tree(self, mmdb, data_callback, node_callback)
373         MMDB_s *mmdb
374         SV *data_callback;
375         SV *node_callback;
376     PREINIT:
377         uint32_t node_num;
378         int depth;
379         int max_depth;
380     CODE:
381         node_num = 0;
382         depth = 1;
383         max_depth = mmdb->metadata.ip_version == 6 ? 128 : 32;
384         mmdb_uint128_t ipnum = 0;
385 
386         iterate_search_nodes(mmdb, data_callback, node_callback, node_num,
387             ipnum, depth, max_depth);
388 
389 void
__read_node(self,mmdb,node_number)390 __read_node(self, mmdb, node_number)
391         MMDB_s *mmdb
392         U32 node_number
393     PREINIT:
394         MMDB_search_node_s node;
395         int status;
396     PPCODE:
397         status = MMDB_read_node(mmdb, node_number, &node);
398         if (MMDB_SUCCESS != status) {
399             const char *error = MMDB_strerror(status);
400             croak(
401                 "MaxMind::DB::Reader::XS - Error trying to read node %i: %s",
402                 node_number, error
403                 );
404         }
405         EXTEND(SP, 2);
406         mPUSHu(node.left_record);
407         mPUSHu(node.right_record);
408 
409 SV *
410 libmaxminddb_version()
411     CODE:
412         const char *v = MMDB_lib_version();
413         RETVAL = newSVpv(v, strlen(v));
414     OUTPUT:
415         RETVAL
416