1use crate::{
2 error::Error,
3 ip2location::{db::LocationDB, record::LocationRecord},
4 ip2proxy::{db::ProxyDB, record::ProxyRecord},
5};
6use memmap2::Mmap;
7use std::{
8 borrow::Cow,
9 net::{IpAddr, Ipv6Addr},
10 path::{Path, PathBuf},
11};
12
13pub const FROM_6TO4: u128 = 0x2002_0000_0000_0000_0000_0000_0000_0000;
15pub const TO_6TO4: u128 = 0x2002_ffff_ffff_ffff_ffff_ffff_ffff_ffff;
17pub const FROM_TEREDO: u128 = 0x2001_0000_0000_0000_0000_0000_0000_0000;
19pub const TO_TEREDO: u128 = 0x2001_0000_ffff_ffff_ffff_ffff_ffff_ffff;
21
22#[derive(Debug)]
27pub enum DB {
28 LocationDb(LocationDB),
30 ProxyDb(ProxyDB),
32}
33
34#[derive(Debug)]
39pub enum Record<'a> {
40 LocationDb(Box<LocationRecord<'a>>),
42 ProxyDb(Box<ProxyRecord<'a>>),
44}
45
46#[derive(Debug)]
51pub(crate) struct Source {
52 path: PathBuf,
53 map: Mmap,
54}
55
56impl std::fmt::Display for Source {
57 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58 write!(f, "{}", self.path.display())
59 }
60}
61
62impl Source {
63 pub fn new(path: PathBuf, map: Mmap) -> Self {
65 Self { path, map }
66 }
67
68 pub fn read_u8(&self, offset: u64) -> Result<u8, Error> {
70 if offset == 0 {
71 return Err(Error::GenericError("read_u8: offset must be >= 1".into()));
72 }
73 let idx = (offset - 1) as usize;
74 self.map.get(idx).copied().ok_or_else(|| {
75 Error::GenericError(format!("read_u8: offset {} out of bounds (len={})", offset, self.map.len()))
76 })
77 }
78
79 pub fn read_u32(&self, offset: u64) -> Result<u32, Error> {
81 if offset == 0 {
82 return Err(Error::GenericError("read_u32: offset must be >= 1".into()));
83 }
84 let start = (offset - 1) as usize;
85 let end = start + 4;
86 let slice = self.map.get(start..end).ok_or_else(|| {
87 Error::GenericError(format!("read_u32: offset {} out of bounds (len={})", offset, self.map.len()))
88 })?;
89 Ok(u32::from_le_bytes(slice.try_into()?))
90 }
91
92 pub fn read_f32(&self, offset: u64) -> Result<f32, Error> {
94 if offset == 0 {
95 return Err(Error::GenericError("read_f32: offset must be >= 1".into()));
96 }
97 let start = (offset - 1) as usize;
98 let end = start + 4;
99 let slice = self.map.get(start..end).ok_or_else(|| {
100 Error::GenericError(format!("read_f32: offset {} out of bounds (len={})", offset, self.map.len()))
101 })?;
102 Ok(f32::from_le_bytes(slice.try_into()?))
103 }
104
105 pub fn read_str(&self, offset: u64) -> Result<Cow<'_, str>, Error> {
111 let len = self.read_u8(offset + 1)? as usize;
112 let start = (offset + 1) as usize;
113 let end = start + len;
114 if end > self.map.len() {
115 return Err(Error::GenericError(format!(
116 "read_str: range {}..{} out of bounds (len={})", start, end, self.map.len()
117 )));
118 }
119 let s = String::from_utf8_lossy(&self.map[start..end]);
120 Ok(s)
121 }
122
123 pub fn read_ipv6(&self, offset: u64) -> Result<Ipv6Addr, Error> {
126 if offset == 0 {
127 return Err(Error::GenericError("read_ipv6: offset must be >= 1".into()));
128 }
129 let start = (offset - 1) as usize;
130 let end = start + 16;
131 if end > self.map.len() {
132 return Err(Error::GenericError(format!(
133 "read_ipv6: range {}..{} out of bounds (len={})", start, end, self.map.len()
134 )));
135 }
136 let mut buf = [0_u8; 16];
137 for i in 0..16 {
138 buf[i] = self.map[start + 15 - i];
139 }
140 Ok(Ipv6Addr::from(buf))
141 }
142}
143
144impl DB {
145 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<DB, Error> {
147 if !path.as_ref().exists() {
158 return Err(Error::IoError(
159 "Error opening DB file: No such file or directory".to_string(),
160 ));
161 }
162
163 let file = std::fs::File::open(&path)?;
164 let map = unsafe { Mmap::map(&file) }?;
168
169 if map.len() < 32 {
171 return Err(Error::GenericError("DB file too small to contain a valid header".into()));
172 }
173 let product_code = map[29]; let source = Source::new(path.as_ref().to_path_buf(), map);
176
177 match product_code {
178 1 => {
179 let mut ldb = LocationDB::new(source);
181 ldb.read_header()?;
182 Ok(DB::LocationDb(ldb))
183 }
184 2 => {
185 let mut pdb = ProxyDB::new(source);
187 pdb.read_header()?;
188 Ok(DB::ProxyDb(pdb))
189 }
190 0 => {
191 let mut ldb = LocationDB::new(source);
193 match ldb.read_header() {
194 Ok(()) => Ok(DB::LocationDb(ldb)),
195 Err(_) => {
196 let file = std::fs::File::open(&path)?;
198 let map = unsafe { Mmap::map(&file) }?;
200 let source = Source::new(path.as_ref().to_path_buf(), map);
201 let mut pdb = ProxyDB::new(source);
202 pdb.read_header()?;
203 Ok(DB::ProxyDb(pdb))
204 }
205 }
206 }
207 _ => Err(Error::UnknownDb),
208 }
209 }
210
211 pub fn print_db_info(&self) {
212 match self {
223 Self::LocationDb(db) => db.print_db_info(),
224 Self::ProxyDb(db) => db.print_db_info(),
225 }
226 }
227
228 pub fn ip_lookup(&self, ip: IpAddr) -> Result<Record<'_>, Error> {
229 match self {
248 Self::LocationDb(db) => Ok(Record::LocationDb(Box::new(db.ip_lookup(ip)?))),
249 Self::ProxyDb(db) => Ok(Record::ProxyDb(Box::new(db.ip_lookup(ip)?))),
250 }
251 }
252}