libloc/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4#[cfg(not(feature = "compat-0-1-1"))]
5compile_error!(
6    "The feature `compat-0-1-1` must be enabled to ensure \
7    forward compatibility with future versions of this crate"
8);
9
10use ipnet::IpNet;
11use ipnet::Ipv4Net;
12use ipnet::Ipv6Net;
13use memmap2::Mmap;
14use std::error::Error;
15use std::fmt;
16use std::fs::File;
17use std::io;
18use std::net::IpAddr;
19use std::net::Ipv4Addr;
20use std::net::Ipv6Addr;
21use std::path::Path;
22use std::str;
23use yoke::Yoke;
24use zerocopy::FromBytes;
25
26mod format;
27
28/// Error type for the [`Locations::open`] function.
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum OpenError {
32    /// Error opening database file.
33    ///
34    /// The file might not exist or you might not have permissions to read it.
35    ///
36    /// The inner error is the one returned from [`std::fs::File::open`].
37    Open(io::Error),
38    /// Error memory-mapping database file.
39    Mmap(io::Error),
40    /// Invalid database file magic, likely not the correct format.
41    InvalidMagic,
42    /// Unsupported database version.
43    UnsupportedVersion(u8),
44    /// Couldn't read database file header, database corrupted.
45    CouldntReadHeader,
46    /// Invalid database header field: `as`, database corrupted.
47    InvalidAsRange,
48    /// Invalid database header field: `network`, database corrupted.
49    InvalidNetworkRange,
50    /// Invalid database header field: `network_node`, database corrupted.
51    InvalidNetworkNodeRange,
52    /// Invalid database header field: `country`, database corrupted.
53    InvalidCountryRange,
54    /// Invalid database header field: `string_pool`, database corrupted.
55    InvalidStringPoolRange,
56}
57
58impl Error for OpenError {
59    fn source(&self) -> Option<&(dyn Error + 'static)> {
60        use self::OpenError::*;
61        match self {
62            Open(e) => Some(e),
63            Mmap(e) => Some(e),
64            InvalidMagic
65            | UnsupportedVersion(_)
66            | CouldntReadHeader
67            | InvalidAsRange
68            | InvalidNetworkRange
69            | InvalidNetworkNodeRange
70            | InvalidCountryRange
71            | InvalidStringPoolRange => None,
72        }
73    }
74}
75
76impl fmt::Display for OpenError {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        use self::OpenError::*;
79        match self {
80            Open(e) => write!(f, "error opening database file: {}", e),
81            Mmap(e) => write!(f, "error memory-mapping database file: {}", e),
82            InvalidMagic => "invalid database file magic, likely not the correct format".fmt(f),
83            UnsupportedVersion(ver) => write!(f, "unsupported database version {}", ver),
84            CouldntReadHeader => "couldn't read database file header, database corrupted".fmt(f),
85            InvalidAsRange => "invalid database header field: as, database corrupted".fmt(f),
86            InvalidNetworkRange => {
87                "invalid database header field: network, database corrupted".fmt(f)
88            }
89            InvalidNetworkNodeRange => {
90                "invalid database header field: network_node, database corrupted".fmt(f)
91            }
92            InvalidCountryRange => {
93                "invalid database header field: country, database corrupted".fmt(f)
94            }
95            InvalidStringPoolRange => {
96                "invalid database header field: string_pool, database corrupted".fmt(f)
97            }
98        }
99    }
100}
101
102/// Information on an [AS] (autonomous system).
103///
104/// Returned by the [`Locations::as_`] function.
105///
106/// [AS]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
107#[derive(Debug)]
108pub struct As<'a> {
109    asn: u32,
110    name: &'a str,
111}
112
113/// Information on an IP network.
114///
115/// Returned by the [`Locations::lookup`] function.
116#[derive(Debug)]
117pub struct Network<'a> {
118    inner: NetworkInner<'a>,
119    addrs: IpNet,
120}
121
122/// Information on an IPv4 network.
123///
124/// See [`Network`].
125#[derive(Debug)]
126pub struct NetworkV4<'a> {
127    inner: NetworkInner<'a>,
128    addrs: Ipv4Net,
129}
130
131/// Information on an IPv6 network.
132///
133/// See [`Network`].
134#[derive(Debug)]
135pub struct NetworkV6<'a> {
136    inner: NetworkInner<'a>,
137    addrs: Ipv6Net,
138}
139
140#[derive(Debug)]
141struct NetworkInner<'a> {
142    // TODO: how to deal with XX? treat it as None?
143    country_code: &'a str,
144    // TODO: how to deal with AS0? treat it as None?
145    asn: u32,
146    flags: u16,
147}
148
149/// Information on a country.
150///
151/// Returned by the [`Locations::country`] function.
152#[derive(Debug)]
153pub struct Country<'a> {
154    code: &'a str,
155    continent_code: &'a str,
156    name: &'a str,
157}
158
159impl<'a> As<'a> {
160    fn from(inner: &LocationsInner<'a>, as_: &'a format::As) -> As<'a> {
161        As {
162            asn: as_.id.get(),
163            name: inner.string(as_.name),
164        }
165    }
166    /// The [ASN] (number) of the [AS].
167    ///
168    /// [AS]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
169    /// [ASN]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
170    pub fn asn(&self) -> u32 {
171        self.asn
172    }
173    /// The human-readable name of the AS.
174    pub fn name(&self) -> &'a str {
175        self.name
176    }
177}
178
179impl<'a> NetworkInner<'a> {
180    fn from(_inner: &LocationsInner<'a>, network: &'a format::Network) -> NetworkInner<'a> {
181        NetworkInner {
182            country_code: str::from_utf8(&network.country_code).unwrap_or_else(|e| {
183                panic!(
184                    "corrupt libloc db: invalid UTF-8 in network country code: {}",
185                    e,
186                );
187            }),
188            asn: network.asn.get(),
189            flags: network.flags.get(),
190        }
191    }
192}
193
194impl<'a> Network<'a> {
195    /// The [ISO 3166-1 alpha-2] country code of the country associated with
196    /// this network.
197    ///
198    /// `"XX"` if unknown.
199    ///
200    /// [ISO 3166-1 alpha-2]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
201    ///
202    /// ```
203    /// use libloc::Locations;
204    ///
205    /// let locations = Locations::open("example-location.db")?;
206    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
207    /// assert_eq!(network.country_code(), "DE");
208    ///
209    /// # Ok::<(), libloc::OpenError>(())
210    /// ```
211    pub fn country_code(&self) -> &'a str {
212        self.inner.country_code
213    }
214    /// The [ASN] of this network.
215    ///
216    /// 0 if unknown.
217    ///
218    /// ```
219    /// use libloc::Locations;
220    ///
221    /// let locations = Locations::open("example-location.db")?;
222    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
223    /// assert_eq!(network.asn(), 204867);
224    ///
225    /// # Ok::<(), libloc::OpenError>(())
226    /// ```
227    ///
228    /// [ASN]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
229    pub fn asn(&self) -> u32 {
230        self.inner.asn
231    }
232    /// Whether the network hosts anonymous proxies.
233    ///
234    /// ```
235    /// use libloc::Locations;
236    ///
237    /// let locations = Locations::open("example-location.db")?;
238    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
239    /// assert_eq!(network.is_anonymous_proxy(), false);
240    ///
241    /// # Ok::<(), libloc::OpenError>(())
242    /// ```
243    ///
244    /// [ASN]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
245    pub fn is_anonymous_proxy(&self) -> bool {
246        self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
247    }
248    /// Whether the network is a satellite provider.
249    ///
250    /// ```
251    /// use libloc::Locations;
252    ///
253    /// let locations = Locations::open("example-location.db")?;
254    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
255    /// assert_eq!(network.is_satellite_provider(), false);
256    ///
257    /// # Ok::<(), libloc::OpenError>(())
258    /// ```
259    ///
260    /// [ASN]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
261    pub fn is_satellite_provider(&self) -> bool {
262        self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
263    }
264    /// Whether the network consists of [anycast] addresses.
265    ///
266    /// ```
267    /// use libloc::Locations;
268    ///
269    /// let locations = Locations::open("example-location.db")?;
270    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
271    /// assert_eq!(network.is_anycast(), true);
272    ///
273    /// # Ok::<(), libloc::OpenError>(())
274    /// ```
275    ///
276    /// [anycast]: https://en.wikipedia.org/wiki/Anycast
277    pub fn is_anycast(&self) -> bool {
278        self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
279    }
280    #[allow(missing_docs)]
281    pub fn is_drop(&self) -> bool {
282        self.inner.flags & format::NETWORK_FLAG_DROP != 0
283    }
284    /// All the addresses belonging to this particular network.
285    ///
286    /// ```
287    /// use libloc::Locations;
288    ///
289    /// let locations = Locations::open("example-location.db")?;
290    /// let network: libloc::Network = locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap();
291    /// assert_eq!(network.addrs().to_string(), "2a07:1c44:5800::/40");
292    ///
293    /// # Ok::<(), libloc::OpenError>(())
294    /// ```
295    ///
296    /// [anycast]: https://en.wikipedia.org/wiki/Anycast
297    pub fn addrs(&self) -> IpNet {
298        self.addrs
299    }
300}
301
302impl<'a> From<NetworkV4<'a>> for Network<'a> {
303    fn from(network: NetworkV4<'a>) -> Network<'a> {
304        Network {
305            inner: network.inner,
306            addrs: network.addrs.into(),
307        }
308    }
309}
310
311impl<'a> From<NetworkV6<'a>> for Network<'a> {
312    fn from(network: NetworkV6<'a>) -> Network<'a> {
313        Network {
314            inner: network.inner,
315            addrs: network.addrs.into(),
316        }
317    }
318}
319
320impl<'a> NetworkV4<'a> {
321    /// See [`Network::country_code`].
322    pub fn country_code(&self) -> &'a str {
323        self.inner.country_code
324    }
325    /// See [`Network::asn`].
326    pub fn asn(&self) -> u32 {
327        self.inner.asn
328    }
329    /// See [`Network::is_anonymous_proxy`].
330    pub fn is_anonymous_proxy(&self) -> bool {
331        self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
332    }
333    /// See [`Network::is_satellite_provider`].
334    pub fn is_satellite_provider(&self) -> bool {
335        self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
336    }
337    /// See [`Network::is_anycast`].
338    pub fn is_anycast(&self) -> bool {
339        self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
340    }
341    /// See [`Network::is_drop`].
342    pub fn is_drop(&self) -> bool {
343        self.inner.flags & format::NETWORK_FLAG_DROP != 0
344    }
345    /// See [`Network::addrs`].
346    pub fn addrs(&self) -> Ipv4Net {
347        self.addrs
348    }
349}
350
351impl<'a> NetworkV6<'a> {
352    /// See [`Network::country_code`].
353    pub fn country_code(&self) -> &'a str {
354        self.inner.country_code
355    }
356    /// See [`Network::asn`].
357    pub fn asn(&self) -> u32 {
358        self.inner.asn
359    }
360    /// See [`Network::is_anonymous_proxy`].
361    pub fn is_anonymous_proxy(&self) -> bool {
362        self.inner.flags & format::NETWORK_FLAG_ANONYMOUS_PROXY != 0
363    }
364    /// See [`Network::is_satellite_provider`].
365    pub fn is_satellite_provider(&self) -> bool {
366        self.inner.flags & format::NETWORK_FLAG_SATTELITE_PROVIDER != 0
367    }
368    /// See [`Network::is_anycast`].
369    pub fn is_anycast(&self) -> bool {
370        self.inner.flags & format::NETWORK_FLAG_ANYCAST != 0
371    }
372    /// See [`Network::is_drop`].
373    pub fn is_drop(&self) -> bool {
374        self.inner.flags & format::NETWORK_FLAG_DROP != 0
375    }
376    /// See [`Network::addrs`].
377    pub fn addrs(&self) -> Ipv6Net {
378        self.addrs
379    }
380}
381
382impl<'a> Country<'a> {
383    fn from(inner: &LocationsInner<'a>, country: &'a format::Country) -> Country<'a> {
384        Country {
385            code: str::from_utf8(&country.code).unwrap_or_else(|e| {
386                panic!("corrupt libloc db: invalid UTF-8 in country code: {}", e);
387            }),
388            continent_code: str::from_utf8(&country.continent_code).unwrap_or_else(|e| {
389                panic!(
390                    "corrupt libloc db: invalid UTF-8 in country continent code: {}",
391                    e,
392                );
393            }),
394            name: inner.string(country.name),
395        }
396    }
397    /// The [ISO 3166-1 alpha-2] code of the country.
398    ///
399    /// It consists of two uppercase latin letters.
400    ///
401    /// [ISO 3166-1 alpha-2]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
402    pub fn code(&self) -> &'a str {
403        self.code
404    }
405    /// The [ISO 3166] code of the continent the country resides in.
406    ///
407    /// - `"AF"` for Africa.
408    /// - `"AN"` for Antarctica.
409    /// - `"AS"` for Asia.
410    /// - `"EU"` for Europe.
411    /// - `"NA"` for North America.
412    /// - `"OC"` for Oceania.
413    /// - `"SA"` for South America.
414    ///
415    /// [ISO 3166]: https://en.wikipedia.org/wiki/ISO_3166
416    pub fn continent_code(&self) -> &'a str {
417        self.continent_code
418    }
419    /// The human-readable name of the country in English.
420    pub fn name(&self) -> &'a str {
421        self.name
422    }
423}
424
425/// A database in libloc format. **Main struct of this crate.**
426pub struct Locations {
427    inner: Yoke<LocationsInner<'static>, Mmap>,
428}
429
430#[cfg_attr(feature = "verified", derive(yoke_derive::Yokeable))]
431struct LocationsInner<'a> {
432    header: &'a format::Header,
433    as_: &'a [format::As],
434    networks: &'a [format::Network],
435    network_nodes: &'a [format::NetworkNode],
436    countries: &'a [format::Country],
437    string_pool: &'a [u8],
438    ipv4_network_node: Option<u32>,
439}
440
441#[cfg(not(feature = "verified"))]
442unsafe impl<'a> yoke::Yokeable<'a> for LocationsInner<'static> {
443    type Output = LocationsInner<'a>;
444    fn transform(&'a self) -> &'a LocationsInner<'a> {
445        self
446    }
447    fn transform_owned(self) -> LocationsInner<'a> {
448        self
449    }
450    unsafe fn make(from: LocationsInner<'a>) -> LocationsInner<'static> {
451        // We're just doing mem::transmute() here, however Rust is
452        // not smart enough to realize that Bar<'a> and Bar<'static> are of
453        // the same size, so instead we use transmute_copy
454        assert!(
455            std::mem::size_of::<LocationsInner<'a>>()
456                == std::mem::size_of::<LocationsInner<'static>>()
457        );
458        let ptr: *const LocationsInner<'static> = (&from as *const LocationsInner<'a>).cast();
459        std::mem::forget(from);
460        std::ptr::read(ptr)
461    }
462    fn transform_mut<F: FnOnce(&'a mut LocationsInner<'a>) + 'static>(&'a mut self, f: F) {
463        unsafe {
464            f(std::mem::transmute::<
465                &mut LocationsInner<'static>,
466                &mut LocationsInner<'a>,
467            >(self))
468        }
469    }
470}
471
472impl<'a> LocationsInner<'a> {
473    fn find_network(&self, root: u32, bits_reverse: u128, num_bits: u32) -> Option<(u8, u32)> {
474        // Walk the tree, remembering the last network we saw.
475        let mut used_bits = 0;
476        let mut bits = bits_reverse;
477        let mut cur = self.network_node(root);
478        let mut last_network = None;
479        for _ in 0..num_bits {
480            let next_index = cur.children[(bits & 1 != 0) as usize].get();
481            if next_index == 0 {
482                break;
483            }
484            last_network = cur.network().map(|n| (used_bits, n)).or(last_network);
485            bits >>= 1;
486            used_bits += 1;
487            cur = self.network_node(next_index);
488        }
489        last_network = cur.network().map(|n| (used_bits, n)).or(last_network);
490        last_network
491    }
492    fn find_network_node(&self, root: u32, bits_reverse: u128, num_bits: u32) -> Option<u32> {
493        // Walk the tree.
494        let mut bits = bits_reverse;
495        let mut cur_index = root;
496        for _ in 0..num_bits {
497            cur_index = self.network_node(cur_index).children[(bits & 1 != 0) as usize].get();
498            if cur_index == 0 {
499                return None;
500            }
501            bits >>= 1;
502        }
503        Some(cur_index)
504    }
505    fn as_(&self, index: u32) -> &'a format::As {
506        let index = index as usize;
507        if index >= self.as_.len() {
508            panic!(
509                "corrupt libloc db: invalid as index: {} > {}",
510                index,
511                self.as_.len(),
512            );
513        }
514        &self.as_[index]
515    }
516    fn network(&self, index: u32) -> &'a format::Network {
517        let index = index as usize;
518        if index >= self.networks.len() {
519            panic!(
520                "corrupt libloc db: invalid network index: {} > {}",
521                index,
522                self.networks.len(),
523            );
524        }
525        &self.networks[index]
526    }
527    fn network_node(&self, index: u32) -> &'a format::NetworkNode {
528        let index = index as usize;
529        if index >= self.network_nodes.len() {
530            panic!(
531                "corrupt libloc db: invalid network node index: {} > {}",
532                index,
533                self.network_nodes.len(),
534            );
535        }
536        &self.network_nodes[index]
537    }
538    fn country(&self, index: u32) -> &'a format::Country {
539        let index = index as usize;
540        if index >= self.countries.len() {
541            panic!(
542                "corrupt libloc db: invalid country index: {} > {}",
543                index,
544                self.countries.len(),
545            );
546        }
547        &self.countries[index]
548    }
549    fn string(&self, str_ref: format::StrRef) -> &'a str {
550        let offset = str_ref.offset.get() as usize;
551        if offset > self.string_pool.len() {
552            panic!(
553                "corrupt libloc db: invalid str_ref: {} > {}",
554                offset,
555                self.string_pool.len(),
556            );
557        }
558        let bytes = &self.string_pool[offset..];
559        let bytes = &bytes[..bytes
560            .iter()
561            .copied()
562            .position(|b| b == 0)
563            .unwrap_or_else(|| {
564                panic!(
565                    "corrupt libloc db: missing null termination for str_ref: {}",
566                    offset,
567                );
568            })];
569        str::from_utf8(bytes).unwrap_or_else(|e| {
570            panic!(
571                "corrupt libloc db: invalid UTF-8 for str_ref: {}: {}",
572                offset, e,
573            )
574        })
575    }
576}
577
578trait ByteSliceExt {
579    fn get_range(&self, range: format::FileRange) -> Option<&[u8]>;
580    fn get_typed_range<T: FromBytes>(&self, range: format::FileRange) -> Option<&[T]>;
581}
582impl<'a> ByteSliceExt for [u8] {
583    fn get_range(&self, range: format::FileRange) -> Option<&[u8]> {
584        let start = range.offset.get();
585        let end = range.offset.get().checked_add(range.length.get())?;
586        self.get(start as usize..end as usize)
587    }
588    fn get_typed_range<T: FromBytes>(&self, range: format::FileRange) -> Option<&[T]> {
589        self.get_range(range).and_then(T::slice_from)
590    }
591}
592
593impl Locations {
594    /// Open a database in libloc format.
595    ///
596    /// # Safety
597    ///
598    /// This memory-maps the database. This is efficient, but you must make
599    /// sure that it's not modified during the usage. See the safety discussion
600    /// of the `Mmap` struct of [`memmap2`](https://docs.rs/memmap2/).
601    ///
602    /// # Errors
603    ///
604    /// Errors can occur when the specified database file cannot be opened for
605    /// reading (e.g. because it does not exist), this is communicated via the
606    /// [`OpenError::Open`] variant.
607    ///
608    /// Additionally, if the opened file is not in a format valid for this
609    /// crate, it is likely that the [`OpenError::InvalidMagic`] variant is
610    /// returned.
611    ///
612    /// If the database is obviously corrupt, e.g. truncated, other errors
613    /// might be returned.
614    ///
615    /// # Examples
616    ///
617    /// ```
618    /// use libloc::Locations;
619    ///
620    /// let locations = Locations::open("example-location.db")?;
621    ///
622    /// // IO errors while opening the file are reported via the `Open(_)`
623    /// // variant.
624    /// assert!(matches!(Locations::open("non-existing"), Err(libloc::OpenError::Open(_))));
625    ///
626    /// // Files that are not in the required format are likely to give the
627    /// // `InvalidMagic` error.
628    /// assert!(matches!(Locations::open("Cargo.toml"), Err(libloc::OpenError::InvalidMagic)));
629    ///
630    /// # Ok::<(), libloc::OpenError>(())
631    /// ```
632    pub fn open<P: AsRef<Path>>(path: P) -> Result<Locations, OpenError> {
633        fn inner(path: &Path) -> Result<Locations, OpenError> {
634            use self::OpenError as Error;
635            let file = File::open(path).map_err(Error::Open)?;
636            let mmap = unsafe { Mmap::map(&file) }.map_err(Error::Mmap)?;
637
638            if !mmap.starts_with(&format::MAGIC) {
639                return Err(Error::InvalidMagic);
640            }
641
642            // This is just an optimization, ignore errors.
643            #[cfg(unix)]
644            let _ = mmap.advise(memmap2::Advice::Random);
645
646            let inner = Yoke::try_attach_to_cart(mmap, |mmap| -> Result<_, Error> {
647                let header =
648                    format::Header::ref_from_prefix(&mmap).ok_or(Error::CouldntReadHeader)?;
649                if header.version != format::VERSION {
650                    return Err(Error::UnsupportedVersion(header.version));
651                }
652
653                let mut inner = LocationsInner {
654                    as_: mmap
655                        .get_typed_range(header.as_)
656                        .ok_or(Error::InvalidAsRange)?,
657                    networks: mmap
658                        .get_typed_range(header.networks)
659                        .ok_or(Error::InvalidNetworkRange)?,
660                    network_nodes: mmap
661                        .get_typed_range(header.network_nodes)
662                        .ok_or(Error::InvalidNetworkNodeRange)?,
663                    countries: mmap
664                        .get_typed_range(header.countries)
665                        .ok_or(Error::InvalidCountryRange)?,
666                    string_pool: mmap
667                        .get_range(header.string_pool)
668                        .ok_or(Error::InvalidStringPoolRange)?,
669
670                    header,
671
672                    ipv4_network_node: Some(u32::MAX), // invalid value
673                };
674                let ipv4_mapped_prefix = u128::from(Ipv4Addr::from(0).to_ipv6_mapped());
675                inner.ipv4_network_node =
676                    inner.find_network_node(0, ipv4_mapped_prefix.reverse_bits(), 96);
677                Ok(inner)
678            })?;
679            Ok(Locations { inner })
680        }
681        inner(path.as_ref())
682    }
683    /// The database creation time.
684    ///
685    /// ```
686    /// use libloc::Locations;
687    ///
688    /// let locations = Locations::open("example-location.db")?;
689    /// assert_eq!(locations.created_at().to_string(), "2024-02-06 22:30:29 UTC");
690    ///
691    /// # Ok::<(), libloc::OpenError>(())
692    /// ```
693    #[cfg(feature = "time")]
694    pub fn created_at(&self) -> chrono::DateTime<chrono::offset::Utc> {
695        let inner = self.inner.get();
696        let created_at = inner.header.created_at.get();
697        chrono::DateTime::from_timestamp(
698            created_at.try_into().unwrap_or_else(|_| {
699                panic!(
700                    "corrupt libloc db: invalid created_at header: {}",
701                    created_at,
702                )
703            }),
704            0,
705        )
706        .unwrap_or_else(|| {
707            panic!(
708                "corrupt libloc db: invalid created_at header: {}",
709                created_at,
710            )
711        })
712    }
713    /// The vendor of the database.
714    ///
715    /// ```
716    /// use libloc::Locations;
717    ///
718    /// let locations = Locations::open("example-location.db")?;
719    /// assert_eq!(locations.vendor(), "IPFire Project");
720    ///
721    /// # Ok::<(), libloc::OpenError>(())
722    /// ```
723    pub fn vendor(&self) -> &str {
724        let inner = self.inner.get();
725        inner.string(inner.header.vendor)
726    }
727    /// The description of the database.
728    ///
729    /// ```
730    /// use libloc::Locations;
731    ///
732    /// let locations = Locations::open("example-location.db")?;
733    /// assert_eq!(locations.description(), "This is a geo location database");
734    ///
735    /// # Ok::<(), libloc::OpenError>(())
736    /// ```
737    pub fn description(&self) -> &str {
738        let inner = self.inner.get();
739        inner.string(inner.header.description)
740    }
741    /// The license of the database.
742    ///
743    /// ```
744    /// use libloc::Locations;
745    ///
746    /// let locations = Locations::open("example-location.db")?;
747    /// assert_eq!(locations.license(), "CC");
748    ///
749    /// # Ok::<(), libloc::OpenError>(())
750    /// ```
751    pub fn license(&self) -> &str {
752        let inner = self.inner.get();
753        inner.string(inner.header.license)
754    }
755    /// Look up an [AS] (autonomous system) by its [ASN] (number).
756    ///
757    /// Returns `None` if it does not appear in the database.
758    ///
759    /// ```
760    /// use libloc::Locations;
761    ///
762    /// let locations = Locations::open("example-location.db")?;
763    /// assert_eq!(locations.as_(204867).unwrap().name(), "Lightning Wire Labs GmbH");
764    /// assert!(matches!(locations.as_(0), None));
765    ///
766    /// # Ok::<(), libloc::OpenError>(())
767    /// ```
768    ///
769    /// [AS]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
770    /// [ASN]: https://en.wikipedia.org/wiki/Autonomous_system_(Internet)
771    pub fn as_(&self, asn: u32) -> Option<As<'_>> {
772        let inner = self.inner.get();
773
774        // The ASs are stored sorted by ASN in the database, so we can use a
775        // binary search to find a particular one.
776        let index = inner
777            .as_
778            .binary_search_by_key(&asn, |as_| as_.id.get())
779            .ok()?;
780        Some(As::from(inner, inner.as_(index.try_into().unwrap())))
781    }
782    /// Look up network information for an IP address.
783    ///
784    /// ```
785    /// use libloc::Locations;
786    ///
787    /// let locations = Locations::open("example-location.db")?;
788    /// assert_eq!(locations.lookup("2a07:1c44:5800::1".parse().unwrap()).unwrap().asn(), 204867);
789    /// assert!(matches!(locations.lookup("127.0.0.1".parse().unwrap()), None));
790    ///
791    /// # Ok::<(), libloc::OpenError>(())
792    /// ```
793    pub fn lookup(&self, addr: IpAddr) -> Option<Network<'_>> {
794        match addr {
795            IpAddr::V4(addr) => self.lookup_v4(addr).map(Into::into),
796            IpAddr::V6(addr) => self.lookup_v6(addr).map(Into::into),
797        }
798    }
799    /// Look up network information for an IPv4 address.
800    ///
801    /// See [`Locations::lookup`].
802    pub fn lookup_v4(&self, addr: Ipv4Addr) -> Option<NetworkV4<'_>> {
803        let inner = self.inner.get();
804
805        let (num_bits, network_idx) = inner.find_network(
806            inner.ipv4_network_node?,
807            u32::from(addr).reverse_bits().into(),
808            32,
809        )?;
810        let addrs = Ipv4Net::new(addr, num_bits).unwrap().trunc();
811
812        Some(NetworkV4 {
813            inner: NetworkInner::from(inner, inner.network(network_idx)),
814            addrs,
815        })
816    }
817    /// Look up network information for an IPv6 address.
818    ///
819    /// See [`Locations::lookup`].
820    pub fn lookup_v6(&self, addr: Ipv6Addr) -> Option<NetworkV6<'_>> {
821        let inner = self.inner.get();
822
823        let (num_bits, network_idx) =
824            inner.find_network(0, u128::from(addr).reverse_bits(), 128)?;
825        let addrs = Ipv6Net::new(addr, num_bits).unwrap().trunc();
826
827        Some(NetworkV6 {
828            inner: NetworkInner::from(inner, inner.network(network_idx)),
829            addrs,
830        })
831    }
832    /// Look up a country by its [ISO 3166-1 alpha-2] code.
833    ///
834    /// [ISO 3166-1 alpha-2]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
835    ///
836    /// ```
837    /// use libloc::Locations;
838    ///
839    /// let locations = Locations::open("example-location.db")?;
840    /// assert_eq!(locations.country("DE").unwrap().name(), "Germany");
841    /// assert!(matches!(locations.country("XX"), None));
842    ///
843    /// # Ok::<(), libloc::OpenError>(())
844    /// ```
845    pub fn country(&self, code: &str) -> Option<Country<'_>> {
846        let inner = self.inner.get();
847
848        if code.len() != 2 {
849            return None;
850        }
851        let code = code.as_bytes();
852        let code = [code[0], code[1]];
853        // The countries are stored sorted by country code in the database, so
854        // we can use a binary search to find a particular one.
855        let index = inner
856            .countries
857            .binary_search_by_key(&code, |c| c.code)
858            .ok()?;
859        Some(Country::from(
860            inner,
861            inner.country(index.try_into().unwrap()),
862        ))
863    }
864}