1#![deny(trivial_casts, trivial_numeric_casts, unused_import_braces)]
2#[cfg(all(feature = "simdutf8", feature = "unsafe-str-decode"))]
78compile_error!("features `simdutf8` and `unsafe-str-decode` are mutually exclusive");
79
80mod decoder;
81mod error;
82pub mod geoip2;
83mod metadata;
84mod reader;
85mod result;
86mod within;
87
88pub use decoder::deserialize_any_with_raw_strings;
90pub use error::MaxMindDbError;
91pub use metadata::Metadata;
92pub use reader::Reader;
93pub use result::{LookupResult, PathElement};
94pub use within::{Within, WithinOptions};
95
96#[cfg(feature = "mmap")]
97pub use memmap2::Mmap;
98
99#[cfg(feature = "fuzzing")]
101#[doc(hidden)]
102pub mod fuzzing {
103 use serde::Deserialize;
104
105 use crate::decoder::{Decoder, VerificationState};
106 use crate::MaxMindDbError;
107
108 pub fn decode<'de, T>(data: &'de [u8]) -> Result<T, MaxMindDbError>
110 where
111 T: Deserialize<'de>,
112 {
113 T::deserialize(&mut Decoder::new(data, 0))
114 }
115
116 pub fn verify(data: &[u8]) -> Result<(), MaxMindDbError> {
118 Decoder::new(data, 0).skip_value_for_verification(&mut VerificationState::default())
119 }
120}
121
122#[cfg(test)]
123mod reader_test;
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use std::net::IpAddr;
129
130 #[test]
131 fn test_lookup_network() {
132 use std::collections::HashMap;
133
134 struct TestCase {
135 ip: &'static str,
136 db_file: &'static str,
137 expected_network: &'static str,
138 expected_found: bool,
139 }
140
141 let test_cases = [
142 TestCase {
144 ip: "1.1.1.1",
145 db_file: "test-data/test-data/MaxMind-DB-test-ipv6-32.mmdb",
146 expected_network: "1.0.0.0/8",
147 expected_found: false,
148 },
149 TestCase {
151 ip: "::1:ffff:ffff",
152 db_file: "test-data/test-data/MaxMind-DB-test-ipv6-24.mmdb",
153 expected_network: "::1:ffff:ffff/128",
154 expected_found: true,
155 },
156 TestCase {
158 ip: "::2:0:1",
159 db_file: "test-data/test-data/MaxMind-DB-test-ipv6-24.mmdb",
160 expected_network: "::2:0:0/122",
161 expected_found: true,
162 },
163 TestCase {
165 ip: "1.1.1.1",
166 db_file: "test-data/test-data/MaxMind-DB-test-ipv4-24.mmdb",
167 expected_network: "1.1.1.1/32",
168 expected_found: true,
169 },
170 TestCase {
172 ip: "1.1.1.3",
173 db_file: "test-data/test-data/MaxMind-DB-test-ipv4-24.mmdb",
174 expected_network: "1.1.1.2/31",
175 expected_found: true,
176 },
177 TestCase {
179 ip: "1.1.1.3",
180 db_file: "test-data/test-data/MaxMind-DB-test-decoder.mmdb",
181 expected_network: "1.1.1.0/24",
182 expected_found: true,
183 },
184 TestCase {
186 ip: "::ffff:1.1.1.128",
187 db_file: "test-data/test-data/MaxMind-DB-test-decoder.mmdb",
188 expected_network: "::ffff:1.1.1.0/120",
189 expected_found: true,
190 },
191 TestCase {
193 ip: "::1.1.1.128",
194 db_file: "test-data/test-data/MaxMind-DB-test-decoder.mmdb",
195 expected_network: "::101:100/120",
196 expected_found: true,
197 },
198 TestCase {
200 ip: "200.0.2.1",
201 db_file: "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb",
202 expected_network: "::/64",
203 expected_found: true,
204 },
205 TestCase {
207 ip: "::200.0.2.1",
208 db_file: "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb",
209 expected_network: "::/64",
210 expected_found: true,
211 },
212 TestCase {
214 ip: "0:0:0:0:ffff:ffff:ffff:ffff",
215 db_file: "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb",
216 expected_network: "::/64",
217 expected_found: true,
218 },
219 TestCase {
221 ip: "ef00::",
222 db_file: "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb",
223 expected_network: "8000::/1",
224 expected_found: false,
225 },
226 ];
227
228 let mut readers: HashMap<&str, Reader<Vec<u8>>> = HashMap::new();
230
231 for test in &test_cases {
232 let reader = readers
233 .entry(test.db_file)
234 .or_insert_with(|| Reader::open_readfile(test.db_file).unwrap());
235
236 let ip: IpAddr = test.ip.parse().unwrap();
237 let result = reader.lookup(ip).unwrap();
238
239 assert_eq!(
240 result.has_data(),
241 test.expected_found,
242 "IP {} in {}: expected has_data={}, got has_data={}",
243 test.ip,
244 test.db_file,
245 test.expected_found,
246 result.has_data()
247 );
248
249 let network = result.network().unwrap();
250 assert_eq!(
251 network.to_string(),
252 test.expected_network,
253 "IP {} in {}: expected network {}, got {}",
254 test.ip,
255 test.db_file,
256 test.expected_network,
257 network
258 );
259 }
260 }
261
262 #[test]
263 fn test_lookup_with_geoip_data() {
264 let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
265 let ip: IpAddr = "89.160.20.128".parse().unwrap();
266
267 let result = reader.lookup(ip).unwrap();
268 assert!(result.has_data(), "lookup should find known IP");
269
270 let city: geoip2::City = result.decode().unwrap().unwrap();
272 assert!(!city.city.is_empty(), "Expected city data");
273
274 let network = result.network().unwrap();
276 assert_eq!(
277 network.to_string(),
278 "89.160.20.128/25",
279 "Expected network 89.160.20.128/25"
280 );
281
282 assert!(
284 result.offset().is_some(),
285 "Expected offset to be Some for found IP"
286 );
287 }
288
289 #[test]
290 fn test_lookup_network_uses_measured_ipv4_subtree_depth() {
291 let mut reader =
292 Reader::open_readfile("test-data/test-data/MaxMind-DB-test-ipv6-32.mmdb").unwrap();
293 assert_eq!(reader.metadata().ip_version, 6);
294
295 reader.ipv4_start_bit_depth = 16;
300
301 let result = reader.lookup("1.1.1.1".parse().unwrap()).unwrap();
302 assert_eq!(result.network().unwrap().to_string(), "1.0.0.0/8");
303 }
304
305 #[test]
306 fn test_lookup_offset_is_stable_for_shared_record() {
307 let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
308
309 let first = reader.lookup("89.160.20.128".parse().unwrap()).unwrap();
310 let second = reader.lookup("89.160.20.129".parse().unwrap()).unwrap();
311
312 assert!(first.has_data());
313 assert!(second.has_data());
314 assert_eq!(first.network().unwrap(), second.network().unwrap());
315 assert_eq!(
316 first.offset(),
317 second.offset(),
318 "IPs in the same record should share a cacheable offset"
319 );
320 }
321
322 #[test]
323 fn test_decode_path() {
324 let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
325 let ip: IpAddr = "89.160.20.128".parse().unwrap();
326
327 let result = reader.lookup(ip).unwrap();
328
329 let iso_code: Option<String> = result
331 .decode_path(&[PathElement::Key("country"), PathElement::Key("iso_code")])
332 .unwrap();
333 assert_eq!(iso_code, Some("SE".to_owned()));
334
335 let missing: Option<String> = result
337 .decode_path(&[PathElement::Key("nonexistent")])
338 .unwrap();
339 assert!(missing.is_none());
340 }
341
342 #[test]
343 fn test_decode_path_on_not_found_lookup() {
344 let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
345 let ip: IpAddr = "2c0f:ff00::1".parse().unwrap();
346
347 let result = reader.lookup(ip).unwrap();
348
349 assert!(!result.has_data());
350 assert!(result.offset().is_none());
351 assert!(result.decode::<geoip2::City>().unwrap().is_none());
352
353 let country_code: Option<String> = result
354 .decode_path(&[PathElement::Key("country"), PathElement::Key("iso_code")])
355 .unwrap();
356 assert!(country_code.is_none());
357 }
358
359 #[test]
360 fn test_ipv6_in_ipv4_database() {
361 let reader =
362 Reader::open_readfile("test-data/test-data/MaxMind-DB-test-ipv4-24.mmdb").unwrap();
363 let ip: IpAddr = "2001::".parse().unwrap();
364
365 let result = reader.lookup(ip);
366 match result {
367 Err(MaxMindDbError::InvalidInput { message }) => {
368 assert!(
369 message.contains("IPv6") && message.contains("IPv4"),
370 "Expected error message about IPv6 in IPv4 database, got: {}",
371 message
372 );
373 }
374 Err(e) => panic!(
375 "Expected InvalidInput error for IPv6 in IPv4 database, got: {:?}",
376 e
377 ),
378 Ok(_) => panic!("Expected error for IPv6 lookup in IPv4-only database"),
379 }
380 }
381
382 #[test]
383 fn test_decode_path_comprehensive() {
384 let reader =
385 Reader::open_readfile("test-data/test-data/MaxMind-DB-test-decoder.mmdb").unwrap();
386 let ip: IpAddr = "::1.1.1.0".parse().unwrap();
387
388 let result = reader.lookup(ip).unwrap();
389 assert!(result.has_data());
390
391 let u16_val: Option<u16> = result.decode_path(&[PathElement::Key("uint16")]).unwrap();
393 assert_eq!(u16_val, Some(100));
394
395 let arr_first: Option<u32> = result
397 .decode_path(&[PathElement::Key("array"), PathElement::Index(0)])
398 .unwrap();
399 assert_eq!(arr_first, Some(1));
400
401 let arr_last: Option<u32> = result
403 .decode_path(&[PathElement::Key("array"), PathElement::Index(2)])
404 .unwrap();
405 assert_eq!(arr_last, Some(3));
406
407 let arr_oob: Option<u32> = result
409 .decode_path(&[PathElement::Key("array"), PathElement::Index(3)])
410 .unwrap();
411 assert!(arr_oob.is_none());
412
413 let arr_last: Option<u32> = result
415 .decode_path(&[PathElement::Key("array"), PathElement::IndexFromEnd(0)])
416 .unwrap();
417 assert_eq!(arr_last, Some(3));
418
419 let arr_first: Option<u32> = result
421 .decode_path(&[PathElement::Key("array"), PathElement::IndexFromEnd(2)])
422 .unwrap();
423 assert_eq!(arr_first, Some(1));
424
425 let nested: Option<u32> = result
427 .decode_path(&[
428 PathElement::Key("map"),
429 PathElement::Key("mapX"),
430 PathElement::Key("arrayX"),
431 PathElement::Index(1),
432 ])
433 .unwrap();
434 assert_eq!(nested, Some(8));
435
436 let missing: Option<u32> = result
438 .decode_path(&[PathElement::Key("does-not-exist"), PathElement::Index(1)])
439 .unwrap();
440 assert!(missing.is_none());
441
442 let utf8: Option<String> = result
444 .decode_path(&[PathElement::Key("utf8_string")])
445 .unwrap();
446 assert_eq!(utf8, Some("unicode! ☯ - ♫".to_owned()));
447 }
448}