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(¤t);
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