ip2location_ip2proxy/
bin_format.rs

1//
2#[cfg(feature = "tokio_fs")]
3pub type TokioFile = async_compat::Compat<tokio::fs::File>;
4
5#[cfg(feature = "async_fs")]
6pub type AsyncFsFile = async_fs::File;
7
8use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
9
10use futures_util::{AsyncRead, AsyncSeek};
11use ip2location_bin_format::querier::{
12    LookupError as QuerierLookupError, NewError as QuerierNewError, Querier,
13};
14
15use crate::record::{OptionRecord, Record, RecordField};
16
17//
18pub struct Database<S> {
19    pub inner: Querier<S>,
20}
21
22impl<S> core::fmt::Debug for Database<S>
23where
24    Querier<S>: core::fmt::Debug,
25{
26    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27        f.debug_struct("Database")
28            .field("inner", &self.inner)
29            .finish()
30    }
31}
32
33#[cfg(feature = "tokio_fs")]
34impl Database<async_compat::Compat<tokio::fs::File>> {
35    pub async fn new(
36        path: impl AsRef<std::path::Path>,
37        pool_max_size: usize,
38    ) -> Result<Self, DatabaseNewError> {
39        use futures_util::TryFutureExt as _;
40
41        let path = path.as_ref().to_owned();
42
43        let inner = Querier::new(
44            || Box::pin(tokio::fs::File::open(path.clone()).map_ok(async_compat::Compat::new)),
45            pool_max_size,
46        )
47        .await
48        .map_err(DatabaseNewError::QuerierNewError)?;
49
50        if !inner.header.r#type.is_ip2proxy() {
51            return Err(DatabaseNewError::TypeMismatch);
52        }
53
54        Ok(Self { inner })
55    }
56}
57
58#[cfg(feature = "async_fs")]
59impl Database<async_fs::File> {
60    pub async fn new(
61        path: impl AsRef<std::path::Path>,
62        pool_max_size: usize,
63    ) -> Result<Self, DatabaseNewError> {
64        let path = path.as_ref().to_owned();
65
66        let inner = Querier::new(
67            || Box::pin(async_fs::File::open(path.clone())),
68            pool_max_size,
69        )
70        .await
71        .map_err(DatabaseNewError::QuerierNewError)?;
72
73        if !inner.header.r#type.is_ip2proxy() {
74            return Err(DatabaseNewError::TypeMismatch);
75        }
76
77        Ok(Self { inner })
78    }
79}
80
81//
82#[derive(Debug)]
83pub enum DatabaseNewError {
84    QuerierNewError(QuerierNewError),
85    TypeMismatch,
86}
87
88impl core::fmt::Display for DatabaseNewError {
89    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90        write!(f, "{self:?}")
91    }
92}
93
94impl std::error::Error for DatabaseNewError {}
95
96//
97//
98//
99impl<S> Database<S>
100where
101    S: AsyncSeek + AsyncRead + Unpin,
102{
103    pub async fn lookup(
104        &self,
105        ip: IpAddr,
106        selected_fields: impl Into<Option<&[RecordField]>>,
107    ) -> Result<Option<Record>, DatabaseLookupError> {
108        match ip {
109            IpAddr::V4(ip) => self.lookup_ipv4(ip, selected_fields).await,
110            IpAddr::V6(ip) => self.lookup_ipv6(ip, selected_fields).await,
111        }
112    }
113
114    pub async fn lookup_ipv4(
115        &self,
116        ip: Ipv4Addr,
117        selected_fields: impl Into<Option<&[RecordField]>>,
118    ) -> Result<Option<Record>, DatabaseLookupError> {
119        let selected_fields: Option<Vec<ip2location_bin_format::record_field::RecordField>> =
120            selected_fields
121                .into()
122                .map(|x| x.iter().map(Into::into).collect::<Vec<_>>());
123        let selected_fields = selected_fields.as_deref();
124
125        //
126        match self
127            .inner
128            .lookup_ipv4(ip, selected_fields)
129            .await
130            .map_err(DatabaseLookupError::QuerierLookupError)?
131        {
132            Some(x) => Ok(OptionRecord::try_from(x)
133                .map_err(DatabaseLookupError::ToRecordFailed)?
134                .0),
135            None => Ok(None),
136        }
137    }
138
139    pub async fn lookup_ipv6(
140        &self,
141        ip: Ipv6Addr,
142        selected_fields: impl Into<Option<&[RecordField]>>,
143    ) -> Result<Option<Record>, DatabaseLookupError> {
144        let selected_fields: Option<Vec<ip2location_bin_format::record_field::RecordField>> =
145            selected_fields
146                .into()
147                .map(|x| x.iter().map(Into::into).collect::<Vec<_>>());
148        let selected_fields = selected_fields.as_deref();
149
150        //
151        match self
152            .inner
153            .lookup_ipv6(ip, selected_fields)
154            .await
155            .map_err(DatabaseLookupError::QuerierLookupError)?
156        {
157            Some(x) => Ok(OptionRecord::try_from(x)
158                .map_err(DatabaseLookupError::ToRecordFailed)?
159                .0),
160            None => Ok(None),
161        }
162    }
163}
164
165//
166#[derive(Debug)]
167pub enum DatabaseLookupError {
168    QuerierLookupError(QuerierLookupError),
169    ToRecordFailed(Box<str>),
170}
171
172impl core::fmt::Display for DatabaseLookupError {
173    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174        write!(f, "{self:?}")
175    }
176}
177
178impl std::error::Error for DatabaseLookupError {}
179
180#[cfg(feature = "tokio_fs")]
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    use std::io::ErrorKind as IoErrorKind;
186
187    #[tokio::test]
188    async fn test_new_and_lookup_latest() -> Result<(), Box<dyn std::error::Error>> {
189        let path_bin = "data/ip2proxy-lite/latest/IP2PROXY-LITE-PX11.BIN";
190
191        let db = match Database::<TokioFile>::new(path_bin, 1).await {
192            Ok(x) => Some(x),
193            Err(DatabaseNewError::QuerierNewError(QuerierNewError::OpenFailed(err)))
194                if err.kind() == IoErrorKind::NotFound =>
195            {
196                None
197            }
198            Err(err) => panic!("{err:?}"),
199        };
200
201        if let Some(db) = db {
202            let record_1 = db
203                .lookup(Ipv4Addr::from(16778241).into(), None)
204                .await?
205                .unwrap();
206            assert_eq!(record_1.country_code.to_string(), "AU");
207
208            let selected_fields = &[RecordField::CountryCodeAndName, RecordField::RegionName];
209            let record_2 = db
210                .lookup(
211                    Ipv6Addr::from(281470698521601).into(),
212                    selected_fields.as_ref(),
213                )
214                .await?
215                .unwrap();
216            assert_eq!(record_2.country_code.to_string(), "AU");
217            println!("{record_2:?}");
218
219            /*
220            20221101 58569071808060804026606586837353981081
221            */
222            if let Some(record_3) = db
223                .lookup(
224                    Ipv6Addr::from(58569071808060804026606586837353981081).into(),
225                    None,
226                )
227                .await?
228            {
229                assert_eq!(record_3.country_code.to_string(), "RW");
230            }
231
232            //
233            let ret = db.lookup(Ipv4Addr::new(8, 8, 8, 8).into(), None).await?;
234            assert!(ret.is_none());
235
236            // google.com
237            let ret = db
238                .lookup(
239                    "2607:f8b0:4009:817::200e"
240                        .parse::<Ipv6Addr>()
241                        .unwrap()
242                        .into(),
243                    None,
244                )
245                .await?;
246            assert!(ret.is_none());
247        }
248
249        Ok(())
250    }
251}