maxminddb/reader.rs
1//! MaxMind DB reader implementation.
2
3use std::collections::HashSet;
4use std::fs;
5use std::net::IpAddr;
6use std::path::Path;
7
8use ipnetwork::IpNetwork;
9use serde::Deserialize;
10
11#[cfg(feature = "mmap")]
12pub use memmap2::Mmap;
13#[cfg(feature = "mmap")]
14use memmap2::MmapOptions;
15#[cfg(feature = "mmap")]
16use std::fs::File;
17
18use crate::decoder;
19use crate::error::MaxMindDbError;
20use crate::metadata::Metadata;
21use crate::result::LookupResult;
22use crate::within::{IpInt, Within, WithinNode, WithinOptions};
23
24/// Size of the data section separator (16 zero bytes).
25const DATA_SECTION_SEPARATOR_SIZE: usize = 16;
26
27/// A reader for the MaxMind DB format. The lifetime `'data` is tied to the
28/// lifetime of the underlying buffer holding the contents of the database file.
29///
30/// The `Reader` supports both file-based and memory-mapped access to MaxMind
31/// DB files, including GeoIP2 and GeoLite2 databases.
32///
33/// # Features
34///
35/// - **`mmap`**: Enable memory-mapped file access for better performance
36/// - **`simdutf8`**: Use SIMD-accelerated UTF-8 validation (faster string
37/// decoding)
38/// - **`unsafe-str-decode`**: Skip UTF-8 validation entirely (unsafe, but
39/// ~20% faster)
40pub struct Reader<S: AsRef<[u8]>> {
41 pub(crate) buf: S,
42 /// Database metadata.
43 pub metadata: Metadata,
44 pub(crate) ipv4_start: usize,
45 /// Bit depth at which ipv4_start was found (0-96). Used to calculate
46 /// correct prefix lengths for IPv4 lookups in IPv6 databases.
47 pub(crate) ipv4_start_bit_depth: usize,
48 pub(crate) pointer_base: usize,
49}
50
51impl<S: AsRef<[u8]>> std::fmt::Debug for Reader<S> {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("Reader")
54 .field("buf_len", &self.buf.as_ref().len())
55 .field("metadata", &self.metadata)
56 .field("ipv4_start", &self.ipv4_start)
57 .field("ipv4_start_bit_depth", &self.ipv4_start_bit_depth)
58 .field("pointer_base", &self.pointer_base)
59 .finish_non_exhaustive()
60 }
61}
62
63#[cfg(feature = "mmap")]
64impl Reader<Mmap> {
65 /// Open a MaxMind DB database file by memory mapping it.
66 ///
67 /// # Safety
68 ///
69 /// The caller must ensure that the database file is not modified or
70 /// truncated while the `Reader` exists. Modifying or truncating the
71 /// file while it is memory-mapped will result in undefined behavior.
72 ///
73 /// # Example
74 ///
75 /// ```
76 /// # #[cfg(feature = "mmap")]
77 /// # {
78 /// // SAFETY: The database file will not be modified while the reader exists.
79 /// let reader = unsafe {
80 /// maxminddb::Reader::open_mmap("test-data/test-data/GeoIP2-City-Test.mmdb")
81 /// }.unwrap();
82 /// # }
83 /// ```
84 pub unsafe fn open_mmap<P: AsRef<Path>>(database: P) -> Result<Reader<Mmap>, MaxMindDbError> {
85 let file_read = File::open(database)?;
86 let mmap = MmapOptions::new()
87 .map(&file_read)
88 .map_err(MaxMindDbError::Mmap)?;
89 Reader::from_source(mmap)
90 }
91}
92
93impl Reader<Vec<u8>> {
94 /// Open a MaxMind DB database file by loading it into memory.
95 ///
96 /// # Example
97 ///
98 /// ```
99 /// let reader = maxminddb::Reader::open_readfile(
100 /// "test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
101 /// ```
102 pub fn open_readfile<P: AsRef<Path>>(database: P) -> Result<Reader<Vec<u8>>, MaxMindDbError> {
103 let buf: Vec<u8> = fs::read(&database)?; // IO error converted via #[from]
104 Reader::from_source(buf)
105 }
106}
107
108impl<'de, S: AsRef<[u8]>> Reader<S> {
109 /// Open a MaxMind DB database from anything that implements AsRef<[u8]>
110 ///
111 /// # Example
112 ///
113 /// ```
114 /// use std::fs;
115 /// let buf = fs::read("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
116 /// let reader = maxminddb::Reader::from_source(buf).unwrap();
117 /// ```
118 pub fn from_source(buf: S) -> Result<Reader<S>, MaxMindDbError> {
119 let metadata_start = find_metadata_start(buf.as_ref())?;
120 let mut type_decoder = decoder::Decoder::new(&buf.as_ref()[metadata_start..], 0);
121 let metadata = Metadata::deserialize(&mut type_decoder)?;
122 if !matches!(metadata.record_size, 24 | 28 | 32) {
123 return Err(MaxMindDbError::invalid_database(format!(
124 "record_size - Expected: 24, 28, or 32 Actual: {}",
125 metadata.record_size
126 )));
127 }
128
129 let search_tree_size = (metadata.node_count as usize) * (metadata.record_size as usize) / 4;
130
131 let mut reader = Reader {
132 buf,
133 pointer_base: search_tree_size + DATA_SECTION_SEPARATOR_SIZE,
134 metadata,
135 ipv4_start: 0,
136 ipv4_start_bit_depth: 0,
137 };
138 let (ipv4_start, ipv4_start_bit_depth) = reader.find_ipv4_start();
139 reader.ipv4_start = ipv4_start;
140 reader.ipv4_start_bit_depth = ipv4_start_bit_depth;
141
142 Ok(reader)
143 }
144
145 /// Lookup an IP address in the database.
146 ///
147 /// Returns a [`LookupResult`] that can be used to:
148 /// - Check if data exists with [`has_data()`](LookupResult::has_data)
149 /// - Get the network containing the IP with [`network()`](LookupResult::network)
150 /// - Decode the full record with [`decode()`](LookupResult::decode)
151 /// - Decode a specific path with [`decode_path()`](LookupResult::decode_path)
152 /// - Get a low-level decoder with [`decoder()`](LookupResult::decoder)
153 ///
154 /// # Examples
155 ///
156 /// Basic city lookup:
157 /// ```
158 /// # use maxminddb::geoip2;
159 /// # use std::net::IpAddr;
160 /// # fn main() -> Result<(), maxminddb::MaxMindDbError> {
161 /// let reader = maxminddb::Reader::open_readfile(
162 /// "test-data/test-data/GeoIP2-City-Test.mmdb")?;
163 ///
164 /// let ip: IpAddr = "89.160.20.128".parse().unwrap();
165 /// let result = reader.lookup(ip)?;
166 ///
167 /// if let Some(city) = result.decode::<geoip2::City>()? {
168 /// // Access nested structs directly - no Option unwrapping needed
169 /// if let Some(name) = city.city.names.english {
170 /// println!("City: {}", name);
171 /// }
172 /// } else {
173 /// println!("No data found for IP {}", ip);
174 /// }
175 /// # Ok(())
176 /// # }
177 /// ```
178 ///
179 /// Selective field access:
180 /// ```
181 /// # use maxminddb::{Reader, PathElement};
182 /// # use std::net::IpAddr;
183 /// # fn main() -> Result<(), maxminddb::MaxMindDbError> {
184 /// let reader = Reader::open_readfile(
185 /// "test-data/test-data/GeoIP2-City-Test.mmdb")?;
186 /// let ip: IpAddr = "89.160.20.128".parse().unwrap();
187 ///
188 /// let result = reader.lookup(ip)?;
189 /// let country_code: Option<String> = result.decode_path(&[
190 /// PathElement::Key("country"),
191 /// PathElement::Key("iso_code"),
192 /// ])?;
193 ///
194 /// println!("Country: {:?}", country_code);
195 /// # Ok(())
196 /// # }
197 /// ```
198 pub fn lookup(&'de self, address: IpAddr) -> Result<LookupResult<'de, S>, MaxMindDbError> {
199 // Check for IPv6 address in IPv4-only database
200 if matches!(address, IpAddr::V6(_)) && self.metadata.ip_version == 4 {
201 return Err(MaxMindDbError::invalid_input(
202 "cannot look up IPv6 address in IPv4-only database",
203 ));
204 }
205
206 let ip_int = IpInt::new(address);
207 let (pointer, prefix_len) = self.find_address_in_tree(&ip_int);
208
209 // For IPv4 addresses in IPv6 databases, adjust prefix_len to reflect
210 // the actual bit depth in the tree. The ipv4_start_bit_depth tells us
211 // how deep in the IPv6 tree we were when we found the IPv4 subtree.
212 let prefix_len = if matches!(address, IpAddr::V4(_)) && self.metadata.ip_version == 6 {
213 self.ipv4_start_bit_depth + prefix_len
214 } else {
215 prefix_len
216 };
217
218 if pointer == 0 {
219 // IP not found in database
220 Ok(LookupResult::new_not_found(self, prefix_len as u8, address))
221 } else {
222 // Resolve the pointer to a data offset
223 let data_offset = self.resolve_data_pointer(pointer)?;
224 Ok(LookupResult::new_found(
225 self,
226 data_offset,
227 prefix_len as u8,
228 address,
229 ))
230 }
231 }
232
233 /// Iterate over all networks in the database.
234 ///
235 /// This is a convenience method equivalent to calling [`within()`](Self::within)
236 /// with `0.0.0.0/0` for IPv4-only databases or `::/0` for IPv6 databases.
237 ///
238 /// # Arguments
239 ///
240 /// * `options` - Controls which networks are yielded. Use [`Default::default()`]
241 /// for standard behavior.
242 ///
243 /// # Examples
244 ///
245 /// Iterate over all networks with default options:
246 /// ```
247 /// use maxminddb::{geoip2, Reader};
248 ///
249 /// let reader = Reader::open_readfile(
250 /// "test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
251 ///
252 /// let mut count = 0;
253 /// for result in reader.networks(Default::default()).unwrap() {
254 /// let lookup = result.unwrap();
255 /// count += 1;
256 /// if count >= 10 { break; }
257 /// }
258 /// ```
259 pub fn networks(&'de self, options: WithinOptions) -> Result<Within<'de, S>, MaxMindDbError> {
260 let cidr = if self.metadata.ip_version == 6 {
261 IpNetwork::V6("::/0".parse().unwrap())
262 } else {
263 IpNetwork::V4("0.0.0.0/0".parse().unwrap())
264 };
265 self.within(cidr, options)
266 }
267
268 /// Iterate over IP networks within a CIDR range.
269 ///
270 /// Returns an iterator that yields [`LookupResult`] for each network in the
271 /// database that falls within the specified CIDR range.
272 ///
273 /// # Arguments
274 ///
275 /// * `cidr` - The CIDR range to iterate over.
276 /// * `options` - Controls which networks are yielded. Use [`Default::default()`]
277 /// for standard behavior (skip aliases, skip networks without data, include
278 /// empty values).
279 ///
280 /// # Examples
281 ///
282 /// Iterate over all IPv4 networks:
283 /// ```
284 /// use ipnetwork::IpNetwork;
285 /// use maxminddb::{geoip2, Reader};
286 ///
287 /// let reader = Reader::open_readfile(
288 /// "test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
289 ///
290 /// let ipv4_all = IpNetwork::V4("0.0.0.0/0".parse().unwrap());
291 /// let mut count = 0;
292 /// for result in reader.within(ipv4_all, Default::default()).unwrap() {
293 /// let lookup = result.unwrap();
294 /// let network = lookup.network().unwrap();
295 /// let city: geoip2::City = lookup.decode().unwrap().unwrap();
296 /// let city_name = city.city.names.english;
297 /// println!("Network: {}, City: {:?}", network, city_name);
298 /// count += 1;
299 /// if count >= 10 { break; } // Limit output for example
300 /// }
301 /// ```
302 ///
303 /// Search within a specific subnet:
304 /// ```
305 /// use ipnetwork::IpNetwork;
306 /// use maxminddb::{geoip2, Reader};
307 ///
308 /// let reader = Reader::open_readfile(
309 /// "test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
310 ///
311 /// let subnet = IpNetwork::V4("192.168.0.0/16".parse().unwrap());
312 /// for result in reader.within(subnet, Default::default()).unwrap() {
313 /// match result {
314 /// Ok(lookup) => {
315 /// let network = lookup.network().unwrap();
316 /// println!("Found: {}", network);
317 /// }
318 /// Err(e) => eprintln!("Error: {}", e),
319 /// }
320 /// }
321 /// ```
322 ///
323 /// Include networks without data:
324 /// ```
325 /// use ipnetwork::IpNetwork;
326 /// use maxminddb::{Reader, WithinOptions};
327 ///
328 /// let reader = Reader::open_readfile(
329 /// "test-data/test-data/MaxMind-DB-test-mixed-24.mmdb").unwrap();
330 ///
331 /// let opts = WithinOptions::default().include_networks_without_data();
332 /// for result in reader.within("1.0.0.0/8".parse().unwrap(), opts).unwrap() {
333 /// let lookup = result.unwrap();
334 /// if !lookup.has_data() {
335 /// println!("Network {} has no data", lookup.network().unwrap());
336 /// }
337 /// }
338 /// ```
339 pub fn within(
340 &'de self,
341 cidr: IpNetwork,
342 options: WithinOptions,
343 ) -> Result<Within<'de, S>, MaxMindDbError> {
344 let ip_address = cidr.network();
345 let prefix_len = cidr.prefix() as usize;
346 let ip_int = IpInt::new(ip_address);
347 let bit_count = ip_int.bit_count();
348
349 let mut node = self.start_node(bit_count);
350 let node_count = self.metadata.node_count as usize;
351
352 let mut stack: Vec<WithinNode> = Vec::with_capacity(bit_count - prefix_len);
353
354 // Traverse down the tree to the level that matches the cidr mark
355 let mut depth = 0_usize;
356 for i in 0..prefix_len {
357 let bit = ip_int.get_bit(i);
358 node = self.read_node(node, bit as usize);
359 depth = i + 1; // We've now traversed i+1 bits (bits 0 through i)
360
361 if node >= node_count {
362 // We've hit a data node or dead end before we exhausted our prefix.
363 // This means the requested CIDR is contained in a single record.
364 break;
365 }
366 }
367
368 // Always push the node - it could be:
369 // - A data node (> node_count): will be yielded as a single record
370 // - The empty node (== node_count): will be skipped unless include_networks_without_data
371 // - An internal node (< node_count): will be traversed to find all contained records
372 stack.push(WithinNode {
373 node,
374 ip_int,
375 prefix_len: depth,
376 });
377
378 let within = Within {
379 reader: self,
380 node_count,
381 stack,
382 options,
383 };
384
385 Ok(within)
386 }
387
388 fn find_address_in_tree(&self, ip_int: &IpInt) -> (usize, usize) {
389 let node_count = self.metadata.node_count as usize;
390
391 let (node, prefix_len) = match *ip_int {
392 IpInt::V4(ip) => {
393 let mut node = self.ipv4_start;
394 let mut prefix_len = 32;
395 for i in 0..32 {
396 if node >= node_count {
397 prefix_len = i;
398 break;
399 }
400 let bit = ((ip >> (31 - i)) & 1) as usize;
401 node = self.read_node(node, bit);
402 }
403 (node, prefix_len)
404 }
405 IpInt::V6(ip) => {
406 let mut node = 0;
407 let mut prefix_len = 128;
408 for i in 0..128 {
409 if node >= node_count {
410 prefix_len = i;
411 break;
412 }
413 let bit = ((ip >> (127 - i)) & 1) as usize;
414 node = self.read_node(node, bit);
415 }
416 (node, prefix_len)
417 }
418 };
419
420 // If node <= node_count, we either hit the placeholder "empty" node
421 // or exhausted all bits but remained on an internal tree node. Treat
422 // both as "not found" to avoid passing a non-data node to
423 // resolve_data_pointer.
424 if node <= node_count {
425 (0, prefix_len)
426 } else {
427 (node, prefix_len)
428 }
429 }
430
431 #[inline]
432 fn start_node(&self, length: usize) -> usize {
433 if length == 128 {
434 0
435 } else {
436 self.ipv4_start
437 }
438 }
439
440 /// Find the IPv4 start node and the bit depth at which it was found.
441 /// Returns (node, depth) where depth is how far into the tree we traversed.
442 fn find_ipv4_start(&self) -> (usize, usize) {
443 if self.metadata.ip_version != 6 {
444 return (0, 0);
445 }
446
447 // We are looking up an IPv4 address in an IPv6 tree. Skip over the
448 // first 96 nodes.
449 let mut node: usize = 0;
450 let mut depth: usize = 0;
451 for i in 0_u8..96 {
452 if node >= self.metadata.node_count as usize {
453 depth = i as usize;
454 break;
455 }
456 node = self.read_node(node, 0);
457 depth = (i + 1) as usize;
458 }
459 (node, depth)
460 }
461
462 #[inline(always)]
463 pub(crate) fn read_node(&self, node_number: usize, index: usize) -> usize {
464 let buf = self.buf.as_ref();
465 let base_offset = node_number * (self.metadata.record_size as usize) / 4;
466
467 match self.metadata.record_size {
468 24 => {
469 let offset = base_offset + index * 3;
470 (buf[offset] as usize) << 16
471 | (buf[offset + 1] as usize) << 8
472 | buf[offset + 2] as usize
473 }
474 28 => {
475 let middle = if index != 0 {
476 buf[base_offset + 3] & 0x0F
477 } else {
478 (buf[base_offset + 3] & 0xF0) >> 4
479 };
480 let offset = base_offset + index * 4;
481 (middle as usize) << 24
482 | (buf[offset] as usize) << 16
483 | (buf[offset + 1] as usize) << 8
484 | buf[offset + 2] as usize
485 }
486 32 => {
487 let offset = base_offset + index * 4;
488 (buf[offset] as usize) << 24
489 | (buf[offset + 1] as usize) << 16
490 | (buf[offset + 2] as usize) << 8
491 | buf[offset + 3] as usize
492 }
493 _ => unreachable!("record_size is validated in Reader::from_source"),
494 }
495 }
496
497 /// Resolves a pointer from the search tree to an offset in the data section.
498 #[inline]
499 pub(crate) fn resolve_data_pointer(&self, pointer: usize) -> Result<usize, MaxMindDbError> {
500 let resolved = pointer - (self.metadata.node_count as usize) - DATA_SECTION_SEPARATOR_SIZE;
501
502 // Check bounds using pointer_base which marks the start of the data section
503 if resolved >= (self.buf.as_ref().len() - self.pointer_base) {
504 return Err(MaxMindDbError::invalid_database(
505 "the MaxMind DB file's data pointer resolves to an invalid location",
506 ));
507 }
508
509 Ok(resolved)
510 }
511
512 /// Performs comprehensive validation of the MaxMind DB file.
513 ///
514 /// This method validates:
515 /// - Metadata section: format versions, required fields, and value constraints
516 /// - Search tree: traverses all networks to verify tree structure integrity
517 /// - Data section separator: validates the 16-byte separator between tree and data
518 /// - Data section: verifies all data records referenced by the search tree
519 ///
520 /// The verifier is stricter than the MaxMind DB specification and may return
521 /// errors on some databases that are still readable by normal operations.
522 /// This method is useful for:
523 /// - Validating database files after download or generation
524 /// - Debugging database corruption issues
525 /// - Ensuring database integrity in critical applications
526 ///
527 /// Note: Verification traverses the entire database and may be slow on large files.
528 /// The method is thread-safe and can be called on an active Reader.
529 ///
530 /// # Example
531 ///
532 /// ```
533 /// use maxminddb::Reader;
534 ///
535 /// let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
536 /// reader.verify().expect("Database should be valid");
537 /// ```
538 pub fn verify(&self) -> Result<(), MaxMindDbError> {
539 self.verify_metadata()?;
540 self.verify_database()
541 }
542
543 fn verify_metadata(&self) -> Result<(), MaxMindDbError> {
544 let m = &self.metadata;
545
546 if m.binary_format_major_version != 2 {
547 return Err(MaxMindDbError::invalid_database(format!(
548 "binary_format_major_version - Expected: 2 Actual: {}",
549 m.binary_format_major_version
550 )));
551 }
552 if m.binary_format_minor_version != 0 {
553 return Err(MaxMindDbError::invalid_database(format!(
554 "binary_format_minor_version - Expected: 0 Actual: {}",
555 m.binary_format_minor_version
556 )));
557 }
558 if m.database_type.is_empty() {
559 return Err(MaxMindDbError::invalid_database(
560 "database_type - Expected: non-empty string Actual: \"\"",
561 ));
562 }
563 if m.description.is_empty() {
564 return Err(MaxMindDbError::invalid_database(
565 "description - Expected: non-empty map Actual: {}",
566 ));
567 }
568 if m.ip_version != 4 && m.ip_version != 6 {
569 return Err(MaxMindDbError::invalid_database(format!(
570 "ip_version - Expected: 4 or 6 Actual: {}",
571 m.ip_version
572 )));
573 }
574 if m.record_size != 24 && m.record_size != 28 && m.record_size != 32 {
575 return Err(MaxMindDbError::invalid_database(format!(
576 "record_size - Expected: 24, 28, or 32 Actual: {}",
577 m.record_size
578 )));
579 }
580 if m.node_count == 0 {
581 return Err(MaxMindDbError::invalid_database(
582 "node_count - Expected: positive integer Actual: 0",
583 ));
584 }
585 Ok(())
586 }
587
588 fn verify_database(&self) -> Result<(), MaxMindDbError> {
589 let offsets = self.verify_search_tree()?;
590 self.verify_data_section_separator()?;
591 self.verify_data_section(offsets)
592 }
593
594 fn verify_search_tree(&self) -> Result<HashSet<usize>, MaxMindDbError> {
595 let mut offsets = HashSet::new();
596 let opts = WithinOptions::default().include_networks_without_data();
597
598 // Maximum number of networks we can expect in a valid database.
599 // A database with N nodes can have at most 2N data entries (each leaf node
600 // can have data). We add some margin for safety.
601 let max_iterations = (self.metadata.node_count as usize).saturating_mul(3);
602 let mut iteration_count = 0usize;
603
604 for result in self.networks(opts)? {
605 let lookup = result?;
606 if let Some(offset) = lookup.offset() {
607 offsets.insert(offset);
608 }
609
610 iteration_count += 1;
611 if iteration_count > max_iterations {
612 return Err(MaxMindDbError::invalid_database(format!(
613 "search tree appears to have a cycle or invalid structure (exceeded {max_iterations} iterations)"
614 )));
615 }
616 }
617 Ok(offsets)
618 }
619
620 fn verify_data_section_separator(&self) -> Result<(), MaxMindDbError> {
621 let separator_start =
622 self.metadata.node_count as usize * self.metadata.record_size as usize / 4;
623 let separator_end = separator_start + DATA_SECTION_SEPARATOR_SIZE;
624
625 if separator_end > self.buf.as_ref().len() {
626 return Err(MaxMindDbError::invalid_database_at(
627 "data section separator extends past end of file",
628 separator_start,
629 ));
630 }
631
632 let separator = &self.buf.as_ref()[separator_start..separator_end];
633
634 for &b in separator {
635 if b != 0 {
636 return Err(MaxMindDbError::invalid_database_at(
637 format!("unexpected byte in data separator: {separator:?}"),
638 separator_start,
639 ));
640 }
641 }
642 Ok(())
643 }
644
645 fn verify_data_section(&self, offsets: HashSet<usize>) -> Result<(), MaxMindDbError> {
646 let data_section = &self.buf.as_ref()[self.pointer_base..];
647
648 // Verify each offset from the search tree points to valid, decodable data
649 for &offset in &offsets {
650 if offset >= data_section.len() {
651 return Err(MaxMindDbError::invalid_database_at(
652 format!(
653 "search tree pointer is beyond data section (len: {})",
654 data_section.len()
655 ),
656 offset,
657 ));
658 }
659
660 let mut dec = decoder::Decoder::new(data_section, offset);
661
662 // Try to skip/decode the value to verify it's valid
663 if let Err(e) = dec.skip_value_for_verification() {
664 return Err(MaxMindDbError::invalid_database_at(
665 format!("decoding error: {e}"),
666 offset,
667 ));
668 }
669 }
670
671 Ok(())
672 }
673}
674
675fn find_metadata_start(buf: &[u8]) -> Result<usize, MaxMindDbError> {
676 const METADATA_START_MARKER: &[u8] = b"\xab\xcd\xefMaxMind.com";
677
678 memchr::memmem::rfind(buf, METADATA_START_MARKER)
679 .map(|x| x + METADATA_START_MARKER.len())
680 .ok_or_else(|| {
681 MaxMindDbError::invalid_database("could not find MaxMind DB metadata in file")
682 })
683}