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