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