├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── geoip.rs ├── codegen ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── examples └── lookup.rs ├── src ├── decoder.rs ├── errors.rs ├── lib.rs ├── metadata.rs ├── models.rs └── reader.rs ├── testdata ├── GeoIP2-Anonymous-IP-Test.mmdb ├── GeoIP2-City-Test.mmdb ├── GeoIP2-Connection-Type-Test.mmdb ├── GeoIP2-Country-Test.mmdb ├── GeoIP2-Domain-Test.mmdb ├── GeoIP2-Enterprise-Test.mmdb ├── GeoIP2-ISP-Test.mmdb ├── GeoLite2-ASN-Test.mmdb ├── GeoLite2-City-Test.mmdb └── GeoLite2-Country-Test.mmdb └── tests ├── dbip.rs └── geoip.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | strategy: 12 | matrix: 13 | platform: [ubuntu-latest, macos-latest, windows-latest] 14 | toolchain: [stable, beta, nightly] 15 | runs-on: ${{ matrix.platform }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install ${{ matrix.toolchain }} toolchain 20 | uses: dtolnay/rust-toolchain@stable 21 | with: 22 | profile: minimal 23 | toolchain: ${{ matrix.toolchain }} 24 | 25 | - name: Run cargo check 26 | run: cargo check 27 | 28 | test: 29 | name: Test Suite 30 | strategy: 31 | matrix: 32 | platform: [ubuntu-latest, macos-latest, windows-latest] 33 | toolchain: [stable, beta, nightly] 34 | runs-on: ${{ matrix.platform }} 35 | steps: 36 | - name: Checkout sources 37 | uses: actions/checkout@v4 38 | with: 39 | submodules: true 40 | 41 | - name: Install ${{ matrix.toolchain }} toolchain 42 | uses: dtolnay/rust-toolchain@stable 43 | with: 44 | toolchain: ${{ matrix.toolchain }} 45 | 46 | - name: Run cargo test 47 | run: cargo test --test geoip 48 | 49 | lints: 50 | name: Lints 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout sources 54 | uses: actions/checkout@v4 55 | 56 | - name: Install stable toolchain 57 | uses: dtolnay/rust-toolchain@stable 58 | with: 59 | toolchain: stable 60 | components: rustfmt, clippy 61 | 62 | - name: Run cargo fmt 63 | run: cargo fmt --all --check 64 | 65 | - name: Run cargo clippy 66 | run: cargo clippy -- -D warnings 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /testdata/GeoIP2-City.mmdb 2 | /testdata/GeoIP2-Connection-Type.mmdb 3 | /testdata/GeoIP2-Country.mmdb 4 | /testdata/GeoIP2-ISP.mmdb 5 | /testdata/GeoLite2-ASN.mmdb 6 | /testdata/GeoLite2-City.mmdb 7 | /testdata/GeoLite2-Country.mmdb 8 | /testdata/dbip-city-lite.mmdb 9 | /testdata/dbip-country-lite.mmdb 10 | /testdata/dbip-asn-lite.mmdb 11 | /target 12 | Cargo.lock 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "geoip2" 3 | version = "0.1.7" 4 | authors = ["IncSW "] 5 | description = "Library for reading MaxMind DB format used by GeoIP2 and GeoLite2" 6 | readme = "README.md" 7 | keywords = ["MaxMind", "GeoIP2", "GeoIP", "geolocation", "ip"] 8 | categories = ["database", "network-programming"] 9 | homepage = "https://github.com/IncSW/geoip2-rs" 10 | documentation = "https://docs.rs/geoip2" 11 | repository = "https://github.com/IncSW/geoip2-rs" 12 | license = "MIT" 13 | edition = "2021" 14 | exclude = ["testdata"] 15 | 16 | [features] 17 | default = [] 18 | unsafe-str = [] 19 | 20 | [dependencies] 21 | geoip2-codegen = "0.1.5" 22 | 23 | [workspace] 24 | members = [".", "codegen"] 25 | 26 | [patch.crates-io] 27 | geoip2-codegen = { path = "codegen" } 28 | 29 | [dev-dependencies] 30 | maxminddb = "0.24.0" 31 | 32 | [profile.release] 33 | lto = "fat" 34 | codegen-units = 1 35 | opt-level = 3 36 | panic = "abort" 37 | 38 | [[test]] 39 | name = "geoip" 40 | 41 | [[test]] 42 | name = "dbip" 43 | 44 | [[bench]] 45 | name = "geoip" 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Aleksey Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

GeoIP2 Reader for Rust

3 |

4 | This library reads MaxMind GeoIP2 databases 5 |

6 | 7 | [![Build Status](https://github.com/IncSW/geoip2-rs/workflows/build/badge.svg)](https://github.com/IncSW/geoip2-rs/actions) 8 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 9 | [![Downloads](https://img.shields.io/crates/d/geoip2.svg)](https://crates.io/crates/geoip2) 10 | 11 | [![crates.io](https://img.shields.io/crates/v/geoip2?label=latest)](https://crates.io/crates/geoip2) 12 | [![Documentation](https://docs.rs/geoip2/badge.svg?version=0.1.7)](https://docs.rs/geoip2/0.1.7) 13 | [![Dependency Status](https://deps.rs/crate/geoip2/0.1.7/status.svg)](https://deps.rs/crate/geoip2/0.1.7) 14 | 15 | 16 |
17 | 18 | ## Usage 19 | 20 | ```toml 21 | [dependencies] 22 | geoip2 = "0.1.7" 23 | ``` 24 | 25 | See [examples/lookup.rs](examples/lookup.rs) for a basic example. 26 | 27 | ## Benchmarks 28 | 29 | Benchmarks required `nightly` Rust. 30 | 31 | Place `GeoIP2-Country.mmdb` and `GeoIP2-City.mmdb` in the `testdata` folder, then run: 32 | ``` 33 | cargo bench 34 | ``` 35 | 36 | Tested on paid DB on cargo 1.56.0-nightly, Intel i7-7700, Debian 9.1. 37 | 38 | ### [IncSW/geoip2-rs](https://github.com/IncSW/geoip2-rs) 39 | `default` 40 | ``` 41 | city 2,175 ns/iter (+/- 124) 42 | country 1,123 ns/iter (+/- 111) 43 | ``` 44 | `unsafe-str` 45 | ``` 46 | city 1,113 ns/iter (+/- 76) 47 | country 524 ns/iter (+/- 31) 48 | ``` 49 | 50 | ### [oschwald/maxminddb-rust](https://github.com/oschwald/maxminddb-rust). 51 | `default` 52 | ``` 53 | city 4,224 ns/iter (+/- 153) 54 | country 2,270 ns/iter (+/- 158) 55 | ``` 56 | `unsafe-str-decode` 57 | ``` 58 | city 3,266 ns/iter (+/- 191) 59 | country 1,802 ns/iter (+/- 75) 60 | ``` 61 | 62 | ## License 63 | 64 | [MIT License](LICENSE). 65 | -------------------------------------------------------------------------------- /benches/geoip.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | extern crate test; 6 | use geoip2::{City, Country, Reader}; 7 | use std::{net::IpAddr, str::FromStr}; 8 | use test::Bencher; 9 | 10 | #[bench] 11 | fn bench_country(b: &mut Bencher) { 12 | let buffer = std::fs::read("./testdata/GeoIP2-Country.mmdb").unwrap(); 13 | let reader = Reader::::from_bytes(&buffer).unwrap(); 14 | let ip = IpAddr::from_str("81.2.69.142").unwrap(); 15 | b.iter(|| { 16 | reader.lookup(ip).unwrap(); 17 | }); 18 | } 19 | 20 | #[bench] 21 | fn bench_city(b: &mut Bencher) { 22 | let buffer = std::fs::read("./testdata/GeoIP2-City.mmdb").unwrap(); 23 | let reader = Reader::::from_bytes(&buffer).unwrap(); 24 | let ip = IpAddr::from_str("81.2.69.142").unwrap(); 25 | b.iter(|| { 26 | reader.lookup(ip).unwrap(); 27 | }); 28 | } 29 | 30 | #[bench] 31 | fn bench_country_oschwald(b: &mut Bencher) { 32 | let reader = maxminddb::Reader::open_readfile("./testdata/GeoIP2-Country.mmdb").unwrap(); 33 | let ip = IpAddr::from_str("81.2.69.142").unwrap(); 34 | b.iter(|| { 35 | reader.lookup::(ip).unwrap(); 36 | }); 37 | } 38 | 39 | #[bench] 40 | fn bench_city_oschwald(b: &mut Bencher) { 41 | let reader = maxminddb::Reader::open_readfile("./testdata/GeoIP2-City.mmdb").unwrap(); 42 | let ip = IpAddr::from_str("81.2.69.142").unwrap(); 43 | b.iter(|| { 44 | reader.lookup::(ip).unwrap(); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "geoip2-codegen" 3 | version = "0.1.5" 4 | authors = ["IncSW "] 5 | description = "geoip2 macros" 6 | readme = "README.md" 7 | homepage = "https://github.com/IncSW/geoip2-rs" 8 | documentation = "https://docs.rs/geoip2-codegen" 9 | repository = "https://github.com/IncSW/geoip2-rs" 10 | license = "MIT" 11 | edition = "2021" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | syn = { version = "2.0.55", features = ["full", "extra-traits"] } 18 | quote = "1.0.35" 19 | proc-macro2 = "1.0.79" 20 | -------------------------------------------------------------------------------- /codegen/README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/geoip2-codegen?label=latest)](https://crates.io/crates/geoip2-codegen) 2 | [![Documentation](https://docs.rs/geoip2-codegen/badge.svg?version=0.1.4)](https://docs.rs/geoip2-codegen/0.1.4) 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 4 | [![Dependency Status](https://deps.rs/crate/geoip2-codegen/0.1.4/status.svg)](https://deps.rs/crate/geoip2-codegen/0.1.4) 5 | ![downloads](https://img.shields.io/crates/d/geoip2-codegen.svg) 6 | -------------------------------------------------------------------------------- /codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | parse_macro_input, 6 | punctuated::Punctuated, 7 | DeriveInput, Fields, FieldsNamed, GenericArgument, Ident, ItemStruct, LitStr, PathArguments, 8 | Result, Token, Type, 9 | }; 10 | 11 | fn extract_field(field_ident: Ident, ty: &Type) -> proc_macro2::TokenStream { 12 | match &ty { 13 | syn::Type::Path(tp) => { 14 | let segment = &tp.path.segments[0]; 15 | let ident = &segment.ident; 16 | match ident.to_string().as_str() { 17 | "Option" => match &segment.arguments { 18 | PathArguments::AngleBracketed(ga) => match &ga.args[0] { 19 | GenericArgument::Type(ty) => match ty { 20 | syn::Type::Path(tp) => { 21 | let segment = &tp.path.segments[0]; 22 | let ident = &segment.ident; 23 | match ident.to_string().as_str() { 24 | "u64" => quote! { 25 | self.#field_ident = Some(read_usize(buffer, offset)? as u64) 26 | }, 27 | "u32" => quote! { 28 | self.#field_ident = Some(read_usize(buffer, offset)? as u32) 29 | }, 30 | "u16" => quote! { 31 | self.#field_ident = Some(read_usize(buffer, offset)? as u16) 32 | }, 33 | "f64" => quote! { 34 | self.#field_ident = Some(read_f64(buffer, offset)?) 35 | }, 36 | "bool" => quote! { 37 | self.#field_ident = Some(read_bool(buffer, offset)?) 38 | }, 39 | "Map" => quote! { 40 | self.#field_ident = Some(read_map(buffer, offset)?) 41 | }, 42 | "models" => { 43 | let ident = &tp.path.segments[1].ident; 44 | quote! { 45 | let mut model = models::#ident::default(); 46 | model.from_bytes(buffer, offset)?; 47 | self.#field_ident = Some(model); 48 | } 49 | } 50 | "Vec" => match &segment.arguments { 51 | PathArguments::AngleBracketed(ga) => match &ga.args[0] { 52 | GenericArgument::Type(syn::Type::Path(p)) => { 53 | let segment = &p.path.segments[0]; 54 | let ident = &segment.ident; 55 | match ident.to_string().as_str() { 56 | "models" => { 57 | let ident = &p.path.segments[1].ident; 58 | quote! { 59 | let (data_type, size) = read_control(buffer, offset)?; 60 | self.#field_ident = Some(match data_type { 61 | DATA_TYPE_SLICE => { 62 | let mut array: Vec> = Vec::with_capacity(size); 63 | for _i in 0..size { 64 | let mut model = models::#ident::default(); 65 | model.from_bytes(buffer, offset)?; 66 | array.push(model); 67 | } 68 | array 69 | } 70 | DATA_TYPE_POINTER => { 71 | let offset = &mut read_pointer(buffer, offset, size)?; 72 | let (data_type, size) = read_control(buffer, offset)?; 73 | match data_type { 74 | DATA_TYPE_SLICE => { 75 | let mut array: Vec> = 76 | Vec::with_capacity(size); 77 | for _ in 0..size { 78 | let mut model = models::#ident::default(); 79 | model.from_bytes(buffer, offset)?; 80 | array.push(model); 81 | } 82 | array 83 | } 84 | _ => return Err(Error::InvalidDataType(data_type)), 85 | } 86 | } 87 | _ => return Err(Error::InvalidDataType(data_type)), 88 | }) 89 | } 90 | } 91 | _ => unimplemented!(), 92 | } 93 | } 94 | _ => unimplemented!("{:?}", &ga.args[0]), 95 | }, 96 | _ => unimplemented!("{:?}", &segment.arguments), 97 | }, 98 | _ => unimplemented!("{:?}", ident), 99 | } 100 | } 101 | syn::Type::Reference(tr) => match tr.elem.as_ref() { 102 | syn::Type::Path(tp) => { 103 | let segment = &tp.path.segments[0]; 104 | let ident = &segment.ident; 105 | match ident.to_string().as_str() { 106 | "str" => quote! { 107 | self.#field_ident = Some(read_str(buffer, offset)?) 108 | }, 109 | _ => unimplemented!("{:?}", ident), 110 | } 111 | } 112 | _ => unimplemented!("{:?}", tr.elem), 113 | }, 114 | _ => unimplemented!("{:?}", ty), 115 | }, 116 | _ => unimplemented!("{:?}", &ga.args[0]), 117 | }, 118 | _ => unimplemented!("{:?}", &segment.arguments), 119 | }, 120 | "Vec" => match &segment.arguments { 121 | PathArguments::AngleBracketed(ga) => match &ga.args[0] { 122 | GenericArgument::Type(ty) => match ty { 123 | syn::Type::Reference(tr) => match tr.elem.as_ref() { 124 | syn::Type::Path(tp) => { 125 | let segment = &tp.path.segments[0]; 126 | let ident = &segment.ident; 127 | match ident.to_string().as_str() { 128 | "str" => quote! { 129 | self.#field_ident = read_array(buffer, offset)? 130 | }, 131 | _ => unimplemented!("{:?}", ident), 132 | } 133 | } 134 | _ => unimplemented!("{:?}", tr.elem), 135 | }, 136 | _ => unimplemented!("{:?}", ty), 137 | }, 138 | _ => unimplemented!("{:?}", &ga.args[0]), 139 | }, 140 | _ => unimplemented!("{:?}", &segment.arguments), 141 | }, 142 | "u64" => quote! { 143 | self.#field_ident = read_usize(buffer, offset)? as u64 144 | }, 145 | "u32" => quote! { 146 | self.#field_ident = read_usize(buffer, offset)? as u32 147 | }, 148 | "u16" => quote! { 149 | self.#field_ident = read_usize(buffer, offset)? as u16 150 | }, 151 | "Map" => quote! { 152 | self.#field_ident = read_map(buffer, offset)? 153 | }, 154 | _ => unimplemented!("{:?}", ident), 155 | } 156 | } 157 | syn::Type::Reference(tr) => match tr.elem.as_ref() { 158 | syn::Type::Path(tp) => { 159 | let segment = &tp.path.segments[0]; 160 | let ident = &segment.ident; 161 | match ident.to_string().as_str() { 162 | "str" => quote! { 163 | self.#field_ident = read_str(buffer, offset)? 164 | }, 165 | _ => unimplemented!("{:?}", ident), 166 | } 167 | } 168 | _ => unimplemented!("{:?}", tr.elem), 169 | }, 170 | _ => unimplemented!("{:?}", ty), 171 | } 172 | } 173 | 174 | fn extract_fields(fields: &Fields) -> Vec { 175 | let fields = if let syn::Fields::Named(FieldsNamed { named, .. }) = fields { 176 | named 177 | } else { 178 | unimplemented!("{:?}", fields); 179 | }; 180 | let mut result = Vec::new(); 181 | for field in fields.iter() { 182 | let field_ident = field.ident.clone().unwrap(); 183 | let mut field_ident_value = format!("{}", field_ident); 184 | if field_ident_value == "country_type" { 185 | field_ident_value = "type".into(); 186 | } 187 | let field_stream = extract_field(field_ident, &field.ty); 188 | result.push(quote! { 189 | #field_ident_value => { 190 | #field_stream 191 | } 192 | }); 193 | } 194 | result 195 | } 196 | 197 | #[proc_macro_derive(Decoder)] 198 | pub fn derive_decoder(input: TokenStream) -> TokenStream { 199 | let DeriveInput { 200 | ident, 201 | generics, 202 | data, 203 | .. 204 | } = parse_macro_input!(input); 205 | 206 | let fields = if let syn::Data::Struct(s) = data { 207 | extract_fields(&s.fields) 208 | } else { 209 | unimplemented!("{:?}", data) 210 | }; 211 | 212 | let output = quote! { 213 | impl<'a> #ident #generics { 214 | pub(crate) fn from_bytes(&mut self, buffer: &'a [u8], offset: &mut usize) -> Result<(), Error> { 215 | let (data_type, size) = read_control(buffer, offset)?; 216 | match data_type { 217 | DATA_TYPE_MAP => self.from_bytes_map(buffer, offset, size), 218 | DATA_TYPE_POINTER => { 219 | let offset = &mut read_pointer(buffer, offset, size)?; 220 | let (data_type, size) = read_control(buffer, offset)?; 221 | match data_type { 222 | DATA_TYPE_MAP => self.from_bytes_map(buffer, offset, size), 223 | _ => return Err(Error::InvalidDataType(data_type)), 224 | } 225 | } 226 | _ => return Err(Error::InvalidDataType(data_type)), 227 | } 228 | } 229 | 230 | fn from_bytes_map( 231 | &mut self, 232 | buffer: &'a [u8], 233 | offset: &mut usize, 234 | size: usize, 235 | ) -> Result<(), Error> { 236 | for _ in 0..size { 237 | match read_str(buffer, offset)? { 238 | #(#fields ,)* 239 | field => return Err(Error::UnknownField(field.into())) 240 | } 241 | } 242 | Ok(()) 243 | } 244 | } 245 | }; 246 | 247 | output.into() 248 | } 249 | 250 | struct Args { 251 | types: Vec, 252 | } 253 | 254 | impl Parse for Args { 255 | fn parse(input: ParseStream) -> Result { 256 | let vars = Punctuated::::parse_terminated(input)?; 257 | Ok(Args { 258 | types: vars.into_iter().collect(), 259 | }) 260 | } 261 | } 262 | 263 | #[proc_macro_attribute] 264 | pub fn reader(metadata: TokenStream, input: TokenStream) -> TokenStream { 265 | let types = parse_macro_input!(metadata as Args).types; 266 | 267 | let types_len = types.len(); 268 | 269 | let input = parse_macro_input!(input as ItemStruct); 270 | let ident = &input.ident; 271 | let generics = &input.generics; 272 | let fields = extract_fields(&input.fields); 273 | 274 | let output = quote! { 275 | #input 276 | 277 | impl<'a> Reader<'a, #ident #generics> { 278 | pub fn from_bytes(buffer: &[u8]) -> Result, Error> { 279 | const types: [&'static str; #types_len] = [#(#types ,)*]; 280 | let reader = Reader::from_bytes_raw(buffer)?; 281 | if !types.contains(&reader.metadata.database_type) { 282 | return Err(Error::InvalidDatabaseType( 283 | reader.metadata.database_type.into(), 284 | )); 285 | } 286 | Ok(reader) 287 | } 288 | 289 | pub fn lookup(&self, address: IpAddr) -> Result<#ident, Error> { 290 | let mut result = #ident::default(); 291 | result.from_bytes(self.decoder_buffer, &mut self.get_offset(address)?)?; 292 | Ok(result) 293 | } 294 | } 295 | 296 | impl<'a> #ident #generics { 297 | pub(crate) fn from_bytes(&mut self, buffer: &'a [u8], offset: &mut usize) -> Result<(), Error> { 298 | let (data_type, size) = read_control(buffer, offset)?; 299 | if data_type != DATA_TYPE_MAP { 300 | return Err(Error::InvalidDataType(data_type)); 301 | } 302 | self.from_bytes_map(buffer, offset, size) 303 | } 304 | 305 | fn from_bytes_map( 306 | &mut self, 307 | buffer: &'a [u8], 308 | offset: &mut usize, 309 | size: usize, 310 | ) -> Result<(), Error> { 311 | for _ in 0..size { 312 | match read_str(buffer, offset)? { 313 | #(#fields ,)* 314 | field => return Err(Error::UnknownField(field.into())) 315 | } 316 | } 317 | Ok(()) 318 | } 319 | } 320 | }; 321 | output.into() 322 | } 323 | -------------------------------------------------------------------------------- /examples/lookup.rs: -------------------------------------------------------------------------------- 1 | use geoip2::{City, Reader}; 2 | use std::{env, fs, net::IpAddr, str::FromStr}; 3 | 4 | fn main() { 5 | let mut args = env::args().skip(1); 6 | let buffer = fs::read(args.next().unwrap()).unwrap(); 7 | let reader = Reader::::from_bytes(&buffer).unwrap(); 8 | let ip = IpAddr::from_str(&args.next().unwrap()).unwrap(); 9 | let result = reader.lookup(ip).unwrap(); 10 | println!("{:#?}", result); 11 | } 12 | -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::Error; 2 | 3 | pub(crate) const DATA_TYPE_EXTENDED: u8 = 0; 4 | pub(crate) const DATA_TYPE_POINTER: u8 = 1; 5 | pub(crate) const DATA_TYPE_STRING: u8 = 2; 6 | pub(crate) const DATA_TYPE_FLOAT64: u8 = 3; 7 | // pub(crate) const DATA_TYPE_BYTES: u8 = 4; 8 | pub(crate) const DATA_TYPE_UINT16: u8 = 5; 9 | pub(crate) const DATA_TYPE_UINT32: u8 = 6; 10 | pub(crate) const DATA_TYPE_MAP: u8 = 7; 11 | pub(crate) const DATA_TYPE_INT32: u8 = 8; 12 | pub(crate) const DATA_TYPE_UINT64: u8 = 9; 13 | pub(crate) const DATA_TYPE_UINT128: u8 = 10; 14 | pub(crate) const DATA_TYPE_SLICE: u8 = 11; 15 | // pub(crate) const DATA_TYPE_DATA_CACHE_CONTAINER: u8 = 12; 16 | // pub(crate) const DATA_TYPE_END_MARKER: u8 = 13; 17 | pub(crate) const DATA_TYPE_BOOL: u8 = 14; 18 | // pub(crate) const DATA_TYPE_FLOAT32: u8 = 15; 19 | 20 | pub(crate) fn read_bytes<'a>( 21 | buffer: &'a [u8], 22 | offset: &mut usize, 23 | size: usize, 24 | ) -> Result<&'a [u8], Error> { 25 | let new_offset = *offset + size; 26 | if new_offset > buffer.len() { 27 | return Err(Error::InvalidOffset); 28 | } 29 | let bytes = &buffer[*offset..new_offset]; 30 | *offset = new_offset; 31 | Ok(bytes) 32 | } 33 | 34 | #[inline] 35 | pub(crate) fn read_control(buffer: &[u8], offset: &mut usize) -> Result<(u8, usize), Error> { 36 | let control_byte = buffer[*offset]; 37 | *offset += 1; 38 | let mut data_type = control_byte >> 5; 39 | if data_type == DATA_TYPE_EXTENDED { 40 | data_type = buffer[*offset] + 7; 41 | *offset += 1; 42 | } 43 | let mut size = (control_byte as usize) & 0x1f; 44 | if data_type == DATA_TYPE_EXTENDED || size < 29 { 45 | return Ok((data_type, size)); 46 | } 47 | let bytes_to_read = size - 28; 48 | size = bytes_to_usize(read_bytes(buffer, offset, bytes_to_read)?); 49 | size += match bytes_to_read { 50 | 1 => 29, 51 | 2 => 285, 52 | _ => 65_821, 53 | }; 54 | Ok((data_type, size)) 55 | } 56 | 57 | pub(crate) fn read_pointer(buffer: &[u8], offset: &mut usize, size: usize) -> Result { 58 | let pointer_size = ((size >> 3) & 0x3) + 1; 59 | let mut prefix = 0usize; 60 | if pointer_size != 4 { 61 | prefix = size & 0x7 62 | } 63 | let unpacked = bytes_to_usize_with_prefix(prefix, read_bytes(buffer, offset, pointer_size)?); 64 | let pointer_value_offset = match pointer_size { 65 | 2 => 2048, 66 | 3 => 526_336, 67 | _ => 0, 68 | }; 69 | Ok(unpacked + pointer_value_offset) 70 | } 71 | 72 | pub(crate) fn read_usize(buffer: &[u8], offset: &mut usize) -> Result { 73 | let (data_type, size) = read_control(buffer, offset)?; 74 | match data_type { 75 | DATA_TYPE_UINT16 | DATA_TYPE_UINT32 | DATA_TYPE_INT32 | DATA_TYPE_UINT64 76 | | DATA_TYPE_UINT128 => Ok(bytes_to_usize(read_bytes(buffer, offset, size)?)), 77 | DATA_TYPE_POINTER => { 78 | let offset = &mut read_pointer(buffer, offset, size)?; 79 | let (data_type, size) = read_control(buffer, offset)?; 80 | match data_type { 81 | DATA_TYPE_UINT16 | DATA_TYPE_UINT32 | DATA_TYPE_INT32 | DATA_TYPE_UINT64 82 | | DATA_TYPE_UINT128 => Ok(bytes_to_usize(read_bytes(buffer, offset, size)?)), 83 | _ => Err(Error::InvalidDataType(data_type)), 84 | } 85 | } 86 | _ => Err(Error::InvalidDataType(data_type)), 87 | } 88 | } 89 | 90 | pub(crate) fn read_bool(buffer: &[u8], offset: &mut usize) -> Result { 91 | let (data_type, size) = read_control(buffer, offset)?; 92 | match data_type { 93 | DATA_TYPE_BOOL => Ok(size != 0), 94 | DATA_TYPE_POINTER => { 95 | let offset = &mut read_pointer(buffer, offset, size)?; 96 | let (data_type, size) = read_control(buffer, offset)?; 97 | match data_type { 98 | DATA_TYPE_BOOL => Ok(size != 0), 99 | _ => Err(Error::InvalidDataType(data_type)), 100 | } 101 | } 102 | _ => Err(Error::InvalidDataType(data_type)), 103 | } 104 | } 105 | 106 | pub(crate) fn read_f64(buffer: &[u8], offset: &mut usize) -> Result { 107 | let (data_type, size) = read_control(buffer, offset)?; 108 | match data_type { 109 | DATA_TYPE_FLOAT64 => Ok(f64::from_bits( 110 | bytes_to_usize(read_bytes(buffer, offset, size)?) as u64, 111 | )), 112 | DATA_TYPE_POINTER => { 113 | let offset = &mut read_pointer(buffer, offset, size)?; 114 | let (data_type, size) = read_control(buffer, offset)?; 115 | match data_type { 116 | DATA_TYPE_FLOAT64 => Ok(f64::from_bits(bytes_to_usize(read_bytes( 117 | buffer, offset, size, 118 | )?) as u64)), 119 | _ => Err(Error::InvalidDataType(data_type)), 120 | } 121 | } 122 | _ => Err(Error::InvalidDataType(data_type)), 123 | } 124 | } 125 | 126 | #[cfg(feature = "unsafe-str")] 127 | pub(crate) fn read_str<'a>(buffer: &'a [u8], offset: &mut usize) -> Result<&'a str, Error> { 128 | let (data_type, size) = read_control(buffer, offset)?; 129 | match data_type { 130 | DATA_TYPE_STRING => { 131 | Ok(unsafe { std::str::from_utf8_unchecked(read_bytes(buffer, offset, size)?) }) 132 | } 133 | DATA_TYPE_POINTER => { 134 | let offset = &mut read_pointer(buffer, offset, size)?; 135 | let (data_type, size) = read_control(buffer, offset)?; 136 | match data_type { 137 | DATA_TYPE_STRING => { 138 | Ok(unsafe { std::str::from_utf8_unchecked(read_bytes(buffer, offset, size)?) }) 139 | } 140 | _ => Err(Error::InvalidDataType(data_type)), 141 | } 142 | } 143 | _ => Err(Error::InvalidDataType(data_type)), 144 | } 145 | } 146 | 147 | #[cfg(not(feature = "unsafe-str"))] 148 | pub(crate) fn read_str<'a>(buffer: &'a [u8], offset: &mut usize) -> Result<&'a str, Error> { 149 | let (data_type, size) = read_control(buffer, offset)?; 150 | match data_type { 151 | DATA_TYPE_STRING => Ok(std::str::from_utf8(read_bytes(buffer, offset, size)?)?), 152 | DATA_TYPE_POINTER => { 153 | let offset = &mut read_pointer(buffer, offset, size)?; 154 | let (data_type, size) = read_control(buffer, offset)?; 155 | match data_type { 156 | DATA_TYPE_STRING => Ok(std::str::from_utf8(read_bytes(buffer, offset, size)?)?), 157 | _ => Err(Error::InvalidDataType(data_type)), 158 | } 159 | } 160 | _ => Err(Error::InvalidDataType(data_type)), 161 | } 162 | } 163 | 164 | #[derive(Default, Debug)] 165 | pub struct Map<'a>(Vec<(&'a str, &'a str)>); 166 | 167 | impl<'a> Map<'a> { 168 | pub fn get(&self, key: &'a str) -> Option<&'a str> { 169 | for tp in self.0.iter() { 170 | if tp.0 == key { 171 | return Some(tp.1); 172 | } 173 | } 174 | None 175 | } 176 | 177 | pub fn iter(&'a self) -> std::slice::Iter<'a, (&'a str, &'a str)> { 178 | self.0.iter() 179 | } 180 | } 181 | 182 | pub(crate) fn read_map<'a>(buffer: &'a [u8], offset: &mut usize) -> Result, Error> { 183 | let (data_type, size) = read_control(buffer, offset)?; 184 | match data_type { 185 | DATA_TYPE_MAP => { 186 | let mut map = Vec::with_capacity(size); 187 | for _ in 0..size { 188 | map.push((read_str(buffer, offset)?, read_str(buffer, offset)?)); 189 | } 190 | Ok(Map(map)) 191 | } 192 | DATA_TYPE_POINTER => { 193 | let offset = &mut read_pointer(buffer, offset, size)?; 194 | let (data_type, size) = read_control(buffer, offset)?; 195 | match data_type { 196 | DATA_TYPE_MAP => { 197 | let mut map = Vec::with_capacity(size); 198 | for _ in 0..size { 199 | map.push((read_str(buffer, offset)?, read_str(buffer, offset)?)); 200 | } 201 | Ok(Map(map)) 202 | } 203 | _ => Err(Error::InvalidDataType(data_type)), 204 | } 205 | } 206 | _ => Err(Error::InvalidDataType(data_type)), 207 | } 208 | } 209 | 210 | pub(crate) fn read_array<'a>(buffer: &'a [u8], offset: &mut usize) -> Result, Error> { 211 | let (data_type, size) = read_control(buffer, offset)?; 212 | match data_type { 213 | DATA_TYPE_SLICE => { 214 | let mut array = Vec::with_capacity(size); 215 | for _ in 0..size { 216 | array.push(read_str(buffer, offset)?); 217 | } 218 | Ok(array) 219 | } 220 | DATA_TYPE_POINTER => { 221 | let offset = &mut read_pointer(buffer, offset, size)?; 222 | let (data_type, size) = read_control(buffer, offset)?; 223 | match data_type { 224 | DATA_TYPE_SLICE => { 225 | let mut array = Vec::with_capacity(size); 226 | for _ in 0..size { 227 | array.push(read_str(buffer, offset)?); 228 | } 229 | Ok(array) 230 | } 231 | _ => Err(Error::InvalidDataType(data_type)), 232 | } 233 | } 234 | _ => Err(Error::InvalidDataType(data_type)), 235 | } 236 | } 237 | 238 | pub(crate) fn bytes_to_usize(buffer: &[u8]) -> usize { 239 | match buffer.len() { 240 | 1 => buffer[0] as usize, 241 | 2 => (buffer[0] as usize) << 8 | (buffer[1] as usize), 242 | 3 => ((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize), 243 | 4 => { 244 | (((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize)) << 8 245 | | (buffer[3] as usize) 246 | } 247 | 5 => { 248 | ((((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize)) << 8 249 | | (buffer[3] as usize)) 250 | << 8 251 | | (buffer[4] as usize) 252 | } 253 | 6 => { 254 | (((((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize)) 255 | << 8 256 | | (buffer[3] as usize)) 257 | << 8 258 | | (buffer[4] as usize)) 259 | << 8 260 | | (buffer[5] as usize) 261 | } 262 | 7 => { 263 | ((((((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize)) 264 | << 8 265 | | (buffer[3] as usize)) 266 | << 8 267 | | (buffer[4] as usize)) 268 | << 8 269 | | (buffer[5] as usize)) 270 | << 8 271 | | (buffer[6] as usize) 272 | } 273 | 8 => { 274 | (((((((buffer[0] as usize) << 8 | (buffer[1] as usize)) << 8 | (buffer[2] as usize)) 275 | << 8 276 | | (buffer[3] as usize)) 277 | << 8 278 | | (buffer[4] as usize)) 279 | << 8 280 | | (buffer[5] as usize)) 281 | << 8 282 | | (buffer[6] as usize)) 283 | << 8 284 | | (buffer[7] as usize) 285 | } 286 | _ => 0, 287 | } 288 | } 289 | 290 | fn bytes_to_usize_with_prefix(prefix: usize, buffer: &[u8]) -> usize { 291 | match buffer.len() { 292 | 0 => prefix, 293 | 1 => prefix << 8 | (buffer[0] as usize), 294 | 2 => (prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize), 295 | 3 => { 296 | ((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 297 | | (buffer[2] as usize) 298 | } 299 | 4 => { 300 | (((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 301 | | (buffer[2] as usize)) 302 | << 8 303 | | (buffer[3] as usize) 304 | } 305 | 5 => { 306 | ((((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 307 | | (buffer[2] as usize)) 308 | << 8 309 | | (buffer[3] as usize)) 310 | << 8 311 | | (buffer[4] as usize) 312 | } 313 | 6 => { 314 | (((((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 315 | | (buffer[2] as usize)) 316 | << 8 317 | | (buffer[3] as usize)) 318 | << 8 319 | | (buffer[4] as usize)) 320 | << 8 321 | | (buffer[5] as usize) 322 | } 323 | 7 => { 324 | ((((((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 325 | | (buffer[2] as usize)) 326 | << 8 327 | | (buffer[3] as usize)) 328 | << 8 329 | | (buffer[4] as usize)) 330 | << 8 331 | | (buffer[5] as usize)) 332 | << 8 333 | | (buffer[6] as usize) 334 | } 335 | 8 => { 336 | (((((((prefix << 8 | (buffer[0] as usize)) << 8 | (buffer[1] as usize)) << 8 337 | | (buffer[2] as usize)) 338 | << 8 339 | | (buffer[3] as usize)) 340 | << 8 341 | | (buffer[4] as usize)) 342 | << 8 343 | | (buffer[5] as usize)) 344 | << 8 345 | | (buffer[6] as usize)) 346 | << 8 347 | | (buffer[7] as usize) 348 | } 349 | _ => 0, 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq)] 2 | pub enum Error { 3 | InvalidMetadata, 4 | InvalidRecordSize(u16), 5 | InvalidDatabaseType(String), 6 | InvalidSearchTreeSize, 7 | InvalidOffset, 8 | InvalidDataType(u8), 9 | InvalidNode, 10 | UnknownField(String), 11 | NotFound, 12 | IPv4Only, 13 | CorruptSearchTree, 14 | 15 | Utf8Error(std::str::Utf8Error), 16 | } 17 | 18 | impl From for Error { 19 | fn from(err: std::str::Utf8Error) -> Error { 20 | Error::Utf8Error(err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod decoder; 2 | mod errors; 3 | mod metadata; 4 | pub mod models; 5 | mod reader; 6 | 7 | pub use errors::Error; 8 | pub use reader::{ 9 | AnonymousIP, City, ConnectionType, Country, Domain, Enterprise, Reader, ASN, ISP, 10 | }; 11 | -------------------------------------------------------------------------------- /src/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::decoder::{ 2 | read_array, read_control, read_map, read_pointer, read_str, read_usize, Map, DATA_TYPE_MAP, 3 | DATA_TYPE_POINTER, 4 | }; 5 | use crate::errors::Error; 6 | use geoip2_codegen::Decoder; 7 | 8 | const METADATA_START_MARKER: [u8; 14] = [ 9 | 0xAB, 0xCD, 0xEF, 0x4d, 0x61, 0x78, 0x4d, 0x69, 0x6e, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 10 | ]; 11 | 12 | #[derive(Default, Debug, Decoder)] 13 | pub struct Metadata<'a> { 14 | pub binary_format_major_version: u16, 15 | pub binary_format_minor_version: u16, 16 | pub node_count: u32, 17 | pub record_size: u16, 18 | pub ip_version: u16, 19 | pub database_type: &'a str, 20 | pub languages: Vec<&'a str>, 21 | pub build_epoch: u64, 22 | pub description: Map<'a>, 23 | } 24 | 25 | impl<'a> Metadata<'a> { 26 | pub(crate) fn find_start(buffer: &[u8]) -> Option { 27 | if buffer.len() < 14 { 28 | return None; 29 | } 30 | let mut i = buffer.len() - 14; 31 | while i != 0 { 32 | i -= 1; 33 | if buffer[i] == METADATA_START_MARKER[0] 34 | && buffer[i + 13] == METADATA_START_MARKER[13] 35 | && buffer[i..i + 14] == METADATA_START_MARKER 36 | { 37 | return Some(i + 14); 38 | } 39 | } 40 | None 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/models.rs: -------------------------------------------------------------------------------- 1 | use crate::decoder::{ 2 | read_bool, read_control, read_f64, read_map, read_pointer, read_str, read_usize, Map, 3 | DATA_TYPE_MAP, DATA_TYPE_POINTER, 4 | }; 5 | use crate::errors::Error; 6 | use geoip2_codegen::Decoder; 7 | 8 | #[derive(Default, Debug, Decoder)] 9 | pub struct Continent<'a> { 10 | pub geoname_id: Option, 11 | pub code: Option<&'a str>, 12 | pub names: Option>, 13 | } 14 | 15 | #[derive(Default, Debug, Decoder)] 16 | pub struct Country<'a> { 17 | pub geoname_id: Option, 18 | pub iso_code: Option<&'a str>, 19 | pub names: Option>, 20 | pub is_in_european_union: Option, 21 | } 22 | 23 | #[derive(Default, Debug, Decoder)] 24 | pub struct EnterpriseCountry<'a> { 25 | pub geoname_id: Option, 26 | pub iso_code: Option<&'a str>, 27 | pub names: Option>, 28 | pub is_in_european_union: Option, 29 | pub confidence: Option, 30 | } 31 | 32 | #[derive(Default, Debug, Decoder)] 33 | pub struct RepresentedCountry<'a> { 34 | pub geoname_id: Option, 35 | pub iso_code: Option<&'a str>, 36 | pub names: Option>, 37 | pub is_in_european_union: Option, 38 | pub country_type: Option<&'a str>, 39 | } 40 | 41 | #[derive(Default, Debug, Decoder)] 42 | pub struct EnterpriseRepresentedCountry<'a> { 43 | pub geoname_id: Option, 44 | pub iso_code: Option<&'a str>, 45 | pub names: Option>, 46 | pub is_in_european_union: Option, 47 | pub country_type: Option<&'a str>, 48 | pub confidence: Option, 49 | } 50 | 51 | #[derive(Default, Debug, Decoder)] 52 | pub struct Subdivision<'a> { 53 | pub geoname_id: Option, 54 | pub iso_code: Option<&'a str>, 55 | pub names: Option>, 56 | } 57 | 58 | #[derive(Default, Debug, Decoder)] 59 | pub struct EnterpriseSubdivision<'a> { 60 | pub geoname_id: Option, 61 | pub iso_code: Option<&'a str>, 62 | pub names: Option>, 63 | pub confidence: Option, 64 | } 65 | 66 | #[derive(Default, Debug, Decoder)] 67 | pub struct City<'a> { 68 | pub geoname_id: Option, 69 | pub names: Option>, 70 | } 71 | 72 | #[derive(Default, Debug, Decoder)] 73 | pub struct EnterpriseCity<'a> { 74 | pub geoname_id: Option, 75 | pub names: Option>, 76 | pub confidence: Option, 77 | } 78 | 79 | #[derive(Default, Debug, Decoder)] 80 | pub struct Location<'a> { 81 | pub latitude: Option, 82 | pub longitude: Option, 83 | pub accuracy_radius: Option, 84 | pub time_zone: Option<&'a str>, 85 | pub metro_code: Option, 86 | } 87 | 88 | #[derive(Default, Debug, Decoder)] 89 | pub struct Postal<'a> { 90 | pub code: Option<&'a str>, 91 | } 92 | 93 | #[derive(Default, Debug, Decoder)] 94 | pub struct EnterprisePostal<'a> { 95 | pub code: Option<&'a str>, 96 | pub confidence: Option, 97 | } 98 | 99 | #[derive(Default, Debug, Decoder)] 100 | pub struct Traits { 101 | pub is_anonymous_proxy: Option, 102 | pub is_satellite_provider: Option, 103 | } 104 | 105 | #[derive(Default, Debug, Decoder)] 106 | pub struct EnterpriseTraits<'a> { 107 | pub is_anonymous_proxy: Option, 108 | pub is_satellite_provider: Option, 109 | pub is_legitimate_proxy: Option, 110 | pub static_ip_score: Option, 111 | pub autonomous_system_number: Option, 112 | pub autonomous_system_organization: Option<&'a str>, 113 | pub isp: Option<&'a str>, 114 | pub organization: Option<&'a str>, 115 | pub mobile_country_code: Option<&'a str>, 116 | pub mobile_network_code: Option<&'a str>, 117 | pub connection_type: Option<&'a str>, 118 | pub domain: Option<&'a str>, 119 | pub user_type: Option<&'a str>, 120 | } 121 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::net::IpAddr; 3 | 4 | use crate::decoder::{ 5 | read_bool, read_control, read_pointer, read_str, read_usize, DATA_TYPE_MAP, DATA_TYPE_POINTER, 6 | DATA_TYPE_SLICE, 7 | }; 8 | use crate::errors::Error; 9 | use crate::metadata::Metadata; 10 | use crate::models; 11 | use geoip2_codegen::reader; 12 | 13 | const DATA_SECTION_SEPARATOR_SIZE: usize = 16; 14 | 15 | pub struct Reader<'a, T> { 16 | t: PhantomData<&'a T>, 17 | pub(crate) metadata: Metadata<'a>, 18 | pub(crate) decoder_buffer: &'a [u8], 19 | node_buffer: &'a [u8], 20 | node_offset_mult: usize, 21 | ip_v4_start: usize, 22 | ip_v4_start_bit_depth: usize, 23 | } 24 | 25 | impl<'a, T> Reader<'a, T> { 26 | fn from_bytes_raw(buffer: &'a [u8]) -> Result, Error> { 27 | let mut metadata_start = match Metadata::find_start(buffer) { 28 | Some(index) => index, 29 | None => return Err(Error::InvalidMetadata), 30 | }; 31 | let mut metadata = Metadata::default(); 32 | metadata.from_bytes(buffer, &mut metadata_start)?; 33 | if metadata.record_size != 24 && metadata.record_size != 28 && metadata.record_size != 32 { 34 | return Err(Error::InvalidRecordSize(metadata.record_size)); 35 | } 36 | let node_offset_mult = (metadata.record_size as usize) / 4; 37 | let search_tree_size = (metadata.node_count as usize) * node_offset_mult; 38 | let data_section_start = search_tree_size + DATA_SECTION_SEPARATOR_SIZE; 39 | if data_section_start > metadata_start { 40 | return Err(Error::InvalidSearchTreeSize); 41 | } 42 | let mut reader = Reader { 43 | t: PhantomData, 44 | metadata, 45 | decoder_buffer: &buffer[data_section_start..metadata_start], 46 | node_buffer: &buffer[..search_tree_size], 47 | node_offset_mult, 48 | ip_v4_start: 0, 49 | ip_v4_start_bit_depth: 0, 50 | }; 51 | if reader.metadata.ip_version == 6 { 52 | let mut node = 0usize; 53 | let mut i = 0usize; 54 | while i < 96 && node < reader.metadata.node_count as usize { 55 | i += 1; 56 | node = reader.read_left(node * node_offset_mult) 57 | } 58 | reader.ip_v4_start = node; 59 | reader.ip_v4_start_bit_depth = i; 60 | } 61 | Ok(reader) 62 | } 63 | 64 | fn find_address_in_tree(&self, ip: &[u8]) -> Result { 65 | let bit_count = ip.len() * 8; 66 | let mut node: usize = if bit_count == 128 { 67 | 0 68 | } else { 69 | self.ip_v4_start 70 | }; 71 | let node_count = self.metadata.node_count as usize; 72 | for i in 0..bit_count { 73 | if node >= node_count { 74 | break; 75 | } 76 | let bit = 1 & (ip[i >> 3] >> (7 - (i % 8))); 77 | let offset = node * self.node_offset_mult; 78 | node = if bit == 0 { 79 | self.read_left(offset) 80 | } else { 81 | self.read_right(offset) 82 | } 83 | } 84 | match node_count { 85 | n if n == node => Ok(0), 86 | n if node > n => Ok(node), 87 | _ => Err(Error::InvalidNode), 88 | } 89 | } 90 | 91 | fn read_left(&self, node_number: usize) -> usize { 92 | match self.metadata.record_size { 93 | 28 => { 94 | (((self.node_buffer[node_number + 3] as usize) & 0xF0) << 20) 95 | | ((self.node_buffer[node_number] as usize) << 16) 96 | | ((self.node_buffer[node_number + 1] as usize) << 8) 97 | | (self.node_buffer[node_number + 2] as usize) 98 | } 99 | 24 => { 100 | ((self.node_buffer[node_number] as usize) << 16) 101 | | ((self.node_buffer[node_number + 1] as usize) << 8) 102 | | (self.node_buffer[node_number + 2] as usize) 103 | } 104 | 32 => { 105 | ((self.node_buffer[node_number] as usize) << 24) 106 | | ((self.node_buffer[node_number + 1] as usize) << 16) 107 | | ((self.node_buffer[node_number + 2] as usize) << 8) 108 | | (self.node_buffer[node_number + 3] as usize) 109 | } 110 | _ => panic!(), 111 | } 112 | } 113 | 114 | fn read_right(&self, node_number: usize) -> usize { 115 | match self.metadata.record_size { 116 | 28 => { 117 | (((self.node_buffer[node_number + 3] as usize) & 0x0F) << 24) 118 | | ((self.node_buffer[node_number + 4] as usize) << 16) 119 | | ((self.node_buffer[node_number + 5] as usize) << 8) 120 | | (self.node_buffer[node_number + 6] as usize) 121 | } 122 | 24 => { 123 | ((self.node_buffer[node_number + 3] as usize) << 16) 124 | | ((self.node_buffer[node_number + 4] as usize) << 8) 125 | | (self.node_buffer[node_number + 5] as usize) 126 | } 127 | 32 => { 128 | ((self.node_buffer[node_number + 4] as usize) << 24) 129 | | ((self.node_buffer[node_number + 5] as usize) << 16) 130 | | ((self.node_buffer[node_number + 6] as usize) << 8) 131 | | (self.node_buffer[node_number + 7] as usize) 132 | } 133 | _ => panic!(), 134 | } 135 | } 136 | 137 | fn lookup_pointer(&self, address: IpAddr) -> Result { 138 | let ip_bytes = match address { 139 | IpAddr::V4(ip) => ip.octets().to_vec(), 140 | IpAddr::V6(ip) => { 141 | if self.metadata.ip_version == 4 { 142 | return Err(Error::IPv4Only); 143 | } 144 | ip.octets().to_vec() 145 | } 146 | }; 147 | let pointer = self.find_address_in_tree(&ip_bytes)?; 148 | if pointer == 0 { 149 | return Err(Error::NotFound); 150 | } 151 | Ok(pointer) 152 | } 153 | 154 | fn get_offset(&self, address: IpAddr) -> Result { 155 | let pointer = self.lookup_pointer(address)?; 156 | let offset = pointer - self.metadata.node_count as usize - DATA_SECTION_SEPARATOR_SIZE; 157 | if offset >= self.decoder_buffer.len() { 158 | return Err(Error::CorruptSearchTree); 159 | } 160 | Ok(offset) 161 | } 162 | 163 | pub fn get_metadata(&self) -> &Metadata<'a> { 164 | &self.metadata 165 | } 166 | } 167 | 168 | #[reader( 169 | "GeoIP2-Country", 170 | "GeoLite2-Country", 171 | "DBIP-Country", 172 | "DBIP-Country-Lite" 173 | )] 174 | #[derive(Default, Debug)] 175 | pub struct Country<'a> { 176 | pub continent: Option>, 177 | pub country: Option>, 178 | pub registered_country: Option>, 179 | pub represented_country: Option>, 180 | pub traits: Option, 181 | } 182 | 183 | #[reader("GeoIP2-City", "GeoLite2-City", "DBIP-City-Lite")] 184 | #[derive(Default, Debug)] 185 | pub struct City<'a> { 186 | pub continent: Option>, 187 | pub country: Option>, 188 | pub subdivisions: Option>>, 189 | pub city: Option>, 190 | pub location: Option>, 191 | pub postal: Option>, 192 | pub registered_country: Option>, 193 | pub represented_country: Option>, 194 | pub traits: Option, 195 | } 196 | 197 | #[reader("GeoIP2-Enterprise")] 198 | #[derive(Default, Debug)] 199 | pub struct Enterprise<'a> { 200 | pub continent: Option>, 201 | pub country: Option>, 202 | pub subdivisions: Option>>, 203 | pub city: Option>, 204 | pub location: Option>, 205 | pub postal: Option>, 206 | pub registered_country: Option>, 207 | pub represented_country: Option>, 208 | pub traits: Option>, 209 | } 210 | 211 | #[reader("GeoIP2-ISP")] 212 | #[derive(Default, Debug)] 213 | pub struct ISP<'a> { 214 | pub autonomous_system_number: Option, 215 | pub autonomous_system_organization: Option<&'a str>, 216 | pub isp: Option<&'a str>, 217 | pub organization: Option<&'a str>, 218 | pub mobile_country_code: Option<&'a str>, 219 | pub mobile_network_code: Option<&'a str>, 220 | } 221 | 222 | #[reader("GeoIP2-Connection-Type")] 223 | #[derive(Default, Debug)] 224 | pub struct ConnectionType<'a> { 225 | pub connection_type: Option<&'a str>, 226 | } 227 | 228 | #[reader("GeoIP2-Anonymous-IP")] 229 | #[derive(Default, Debug)] 230 | pub struct AnonymousIP { 231 | pub is_anonymous: Option, 232 | pub is_anonymous_vpn: Option, 233 | pub is_hosting_provider: Option, 234 | pub is_public_proxy: Option, 235 | pub is_tor_exit_node: Option, 236 | pub is_residential_proxy: Option, 237 | } 238 | 239 | #[reader("GeoLite2-ASN", "DBIP-ASN-Lite", "DBIP-ASN-Lite (compat=GeoLite2-ASN)")] 240 | #[derive(Default, Debug)] 241 | pub struct ASN<'a> { 242 | pub autonomous_system_number: Option, 243 | pub autonomous_system_organization: Option<&'a str>, 244 | } 245 | 246 | #[reader("GeoIP2-Domain")] 247 | #[derive(Default, Debug)] 248 | pub struct Domain<'a> { 249 | pub domain: Option<&'a str>, 250 | } 251 | -------------------------------------------------------------------------------- /testdata/GeoIP2-Anonymous-IP-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-Anonymous-IP-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-City-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-City-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-Connection-Type-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-Connection-Type-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-Country-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-Country-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-Domain-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-Domain-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-Enterprise-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-Enterprise-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoIP2-ISP-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoIP2-ISP-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoLite2-ASN-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoLite2-ASN-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoLite2-City-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoLite2-City-Test.mmdb -------------------------------------------------------------------------------- /testdata/GeoLite2-Country-Test.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IncSW/geoip2-rs/30537c9567882c7986d51ee6a8f51d5adffff474/testdata/GeoLite2-Country-Test.mmdb -------------------------------------------------------------------------------- /tests/dbip.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use geoip2::{City, Country, Reader, ASN}; 4 | use std::{net::IpAddr, str::FromStr}; 5 | 6 | #[test] 7 | fn test_city() { 8 | let buffer = std::fs::read("./testdata/dbip-city-lite.mmdb").unwrap(); 9 | let reader = Reader::::from_bytes(&buffer).unwrap(); 10 | 11 | let result = reader 12 | .lookup(IpAddr::from_str("66.30.184.198").unwrap()) 13 | .unwrap(); 14 | 15 | let city = result.city.unwrap(); 16 | assert_eq!(city.geoname_id, None); 17 | let names = city.names.unwrap(); 18 | assert_eq!(names.get("en"), Some("Medfield")); 19 | 20 | let location = result.location.unwrap(); 21 | assert_eq!(location.latitude, Some(42.1876)); 22 | assert_eq!(location.longitude, Some(-71.3065)); 23 | 24 | let subdivisions = result.subdivisions.unwrap(); 25 | assert_eq!(subdivisions.len(), 1); 26 | let subdivision = &subdivisions[0]; 27 | let names = subdivision.names.as_ref().unwrap(); 28 | assert_eq!(names.get("en"), Some("Massachusetts")); 29 | } 30 | 31 | #[test] 32 | fn test_country() { 33 | let buffer = std::fs::read("./testdata/dbip-country-lite.mmdb").unwrap(); 34 | let reader = Reader::::from_bytes(&buffer).unwrap(); 35 | 36 | let result = reader 37 | .lookup(IpAddr::from_str("66.30.184.198").unwrap()) 38 | .unwrap(); 39 | let continent = result.continent.unwrap(); 40 | assert_eq!(continent.geoname_id, Some(6255149)); 41 | assert_eq!(continent.code, Some("NA")); 42 | let names = continent.names.unwrap(); 43 | assert_eq!(names.get("en"), Some("North America")); 44 | assert_eq!(names.get("ru"), Some("Северная Америка")); 45 | 46 | let country = result.country.unwrap(); 47 | assert_eq!(country.geoname_id, Some(6252001)); 48 | assert_eq!(country.iso_code, Some("US")); 49 | let names = country.names.unwrap(); 50 | assert_eq!(names.get("fr"), Some("États-Unis")); 51 | assert_eq!(names.get("pt-BR"), Some("Estados Unidos")); 52 | assert_eq!(country.is_in_european_union, Some(false)); 53 | } 54 | 55 | #[test] 56 | fn test_asn() { 57 | let buffer = std::fs::read("./testdata/dbip-asn-lite.mmdb").unwrap(); 58 | let reader = Reader::::from_bytes(&buffer).unwrap(); 59 | 60 | let result = reader 61 | .lookup(IpAddr::from_str("66.30.184.198").unwrap()) 62 | .unwrap(); 63 | assert_eq!(result.autonomous_system_number, Some(7015)); 64 | assert_eq!( 65 | result.autonomous_system_organization, 66 | Some("Comcast Cable Communications, LLC") 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/geoip.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use geoip2::{ 4 | AnonymousIP, City, ConnectionType, Country, Domain, Enterprise, Error, Reader, ASN, ISP, 5 | }; 6 | use std::{net::IpAddr, str::FromStr}; 7 | 8 | #[test] 9 | fn test_invalid_database_type() { 10 | let buffer = std::fs::read("./testdata/GeoIP2-Anonymous-IP-Test.mmdb").unwrap(); 11 | let reader = Reader::::from_bytes(&buffer); 12 | if let Err(Error::InvalidDatabaseType(msg)) = reader { 13 | assert_eq!(msg, "GeoIP2-Anonymous-IP"); 14 | return; 15 | } 16 | assert!(false); 17 | } 18 | 19 | #[test] 20 | fn test_anonymous_ip() { 21 | let buffer = std::fs::read("./testdata/GeoIP2-Anonymous-IP-Test.mmdb").unwrap(); 22 | let reader = Reader::::from_bytes(&buffer).unwrap(); 23 | { 24 | let result = reader 25 | .lookup(IpAddr::from_str("81.2.69.0").unwrap()) 26 | .unwrap(); 27 | assert_eq!(result.is_anonymous, Some(true)); 28 | assert_eq!(result.is_anonymous_vpn, Some(true)); 29 | assert_eq!(result.is_hosting_provider, Some(true)); 30 | assert_eq!(result.is_public_proxy, Some(true)); 31 | assert_eq!(result.is_tor_exit_node, Some(true)); 32 | assert_eq!(result.is_residential_proxy, Some(true)); 33 | } 34 | { 35 | let result = reader 36 | .lookup(IpAddr::from_str("186.30.236.0").unwrap()) 37 | .unwrap(); 38 | assert_eq!(result.is_anonymous, Some(true)); 39 | assert_eq!(result.is_anonymous_vpn, None); 40 | assert_eq!(result.is_hosting_provider, None); 41 | assert_eq!(result.is_public_proxy, Some(true)); 42 | assert_eq!(result.is_tor_exit_node, None); 43 | assert_eq!(result.is_residential_proxy, None); 44 | } 45 | } 46 | 47 | #[test] 48 | fn test_enterprise() { 49 | let buffer = std::fs::read("./testdata/GeoIP2-Enterprise-Test.mmdb").unwrap(); 50 | let reader = Reader::::from_bytes(&buffer).unwrap(); 51 | { 52 | let result = reader 53 | .lookup(IpAddr::from_str("74.209.24.0").unwrap()) 54 | .unwrap(); 55 | 56 | let city = result.city.unwrap(); 57 | assert_eq!(city.confidence, Some(11)); 58 | 59 | let country = result.country.unwrap(); 60 | assert_eq!(country.confidence, Some(99)); 61 | 62 | let postal = result.postal.unwrap(); 63 | assert_eq!(postal.code, Some("12037")); 64 | assert_eq!(postal.confidence, Some(11)); 65 | 66 | let subdivisions = result.subdivisions.unwrap(); 67 | assert_eq!(subdivisions.len(), 1); 68 | let subdivision = &subdivisions[0]; 69 | assert_eq!(subdivision.confidence, Some(93)); 70 | 71 | let traits = result.traits.unwrap(); 72 | assert_eq!(traits.autonomous_system_number, Some(14671)); 73 | assert_eq!( 74 | traits.autonomous_system_organization, 75 | Some("FairPoint Communications") 76 | ); 77 | assert_eq!(traits.isp, Some("Fairpoint Communications")); 78 | assert_eq!(traits.organization, Some("Fairpoint Communications")); 79 | assert_eq!(traits.connection_type, Some("Cable/DSL")); 80 | assert_eq!(traits.domain, Some("frpt.net")); 81 | assert_eq!(traits.static_ip_score, Some(0.34)); 82 | assert_eq!(traits.user_type, Some("residential")); 83 | } 84 | { 85 | let result = reader 86 | .lookup(IpAddr::from_str("81.2.69.160").unwrap()) 87 | .unwrap(); 88 | 89 | let traits = result.traits.unwrap(); 90 | assert_eq!(traits.isp, Some("Andrews & Arnold Ltd")); 91 | assert_eq!(traits.organization, Some("STONEHOUSE office network")); 92 | assert_eq!(traits.connection_type, Some("Corporate")); 93 | assert_eq!(traits.domain, Some("in-addr.arpa")); 94 | assert_eq!(traits.static_ip_score, Some(0.34)); 95 | assert_eq!(traits.user_type, Some("government")); 96 | } 97 | } 98 | 99 | #[test] 100 | fn test_city() { 101 | let buffer = std::fs::read("./testdata/GeoIP2-City-Test.mmdb").unwrap(); 102 | let reader = Reader::::from_bytes(&buffer).unwrap(); 103 | { 104 | let result = reader 105 | .lookup(IpAddr::from_str("81.2.69.142").unwrap()) 106 | .unwrap(); 107 | 108 | let city = result.city.unwrap(); 109 | assert_eq!(city.geoname_id, Some(2643743)); 110 | let names = city.names.unwrap(); 111 | assert_eq!(names.get("de"), Some("London")); 112 | assert_eq!(names.get("es"), Some("Londres")); 113 | 114 | let location = result.location.unwrap(); 115 | assert_eq!(location.accuracy_radius, Some(10)); 116 | assert_eq!(location.latitude, Some(51.5142)); 117 | assert_eq!(location.longitude, Some(-0.0931)); 118 | assert_eq!(location.time_zone, Some("Europe/London")); 119 | 120 | let subdivisions = result.subdivisions.unwrap(); 121 | assert_eq!(subdivisions.len(), 1); 122 | let subdivision = &subdivisions[0]; 123 | assert_eq!(subdivision.geoname_id, Some(6269131)); 124 | assert_eq!(subdivision.iso_code, Some("ENG")); 125 | let names = subdivision.names.as_ref().unwrap(); 126 | assert_eq!(names.get("en"), Some("England")); 127 | assert_eq!(names.get("pt-BR"), Some("Inglaterra")); 128 | } 129 | { 130 | let result = reader 131 | .lookup(IpAddr::from_str("2a02:ff80::").unwrap()) 132 | .unwrap(); 133 | 134 | assert!(result.city.is_none()); 135 | 136 | let country = result.country.unwrap(); 137 | assert_eq!(country.is_in_european_union, Some(true)); 138 | 139 | let location = result.location.unwrap(); 140 | assert_eq!(location.accuracy_radius, Some(100)); 141 | assert_eq!(location.latitude, Some(51.5)); 142 | assert_eq!(location.longitude, Some(10.5)); 143 | assert_eq!(location.time_zone, Some("Europe/Berlin")); 144 | 145 | assert!(result.subdivisions.is_none()); 146 | } 147 | } 148 | 149 | #[test] 150 | fn test_connection_type() { 151 | let buffer = std::fs::read("./testdata/GeoIP2-Connection-Type-Test.mmdb").unwrap(); 152 | let reader = Reader::::from_bytes(&buffer).unwrap(); 153 | { 154 | let result = reader 155 | .lookup(IpAddr::from_str("1.0.0.0").unwrap()) 156 | .unwrap() 157 | .connection_type 158 | .unwrap(); 159 | assert_eq!(result, "Dialup"); 160 | } 161 | { 162 | let result = reader 163 | .lookup(IpAddr::from_str("1.0.1.0").unwrap()) 164 | .unwrap() 165 | .connection_type 166 | .unwrap(); 167 | assert_eq!(result, "Cable/DSL"); 168 | } 169 | } 170 | 171 | #[test] 172 | fn test_country() { 173 | let buffer = std::fs::read("./testdata/GeoIP2-Country-Test.mmdb").unwrap(); 174 | let reader = Reader::::from_bytes(&buffer).unwrap(); 175 | { 176 | let result = reader 177 | .lookup(IpAddr::from_str("74.209.24.0").unwrap()) 178 | .unwrap(); 179 | let continent = result.continent.unwrap(); 180 | assert_eq!(continent.geoname_id, Some(6255149)); 181 | assert_eq!(continent.code, Some("NA")); 182 | let names = continent.names.unwrap(); 183 | assert_eq!(names.get("es"), Some("Norteamérica")); 184 | assert_eq!(names.get("ru"), Some("Северная Америка")); 185 | 186 | let country = result.country.unwrap(); 187 | assert_eq!(country.geoname_id, Some(6252001)); 188 | assert_eq!(country.iso_code, Some("US")); 189 | let names = country.names.unwrap(); 190 | assert_eq!(names.get("fr"), Some("États-Unis")); 191 | assert_eq!(names.get("pt-BR"), Some("Estados Unidos")); 192 | assert_eq!(country.is_in_european_union, None); 193 | 194 | let registered_country = result.registered_country.unwrap(); 195 | assert_eq!(registered_country.geoname_id, Some(6252001)); 196 | 197 | assert!(result.represented_country.is_none()); 198 | 199 | let traits = result.traits.unwrap(); 200 | assert_eq!(traits.is_anonymous_proxy, Some(true)); 201 | assert_eq!(traits.is_satellite_provider, Some(true)); 202 | } 203 | { 204 | let result = reader 205 | .lookup(IpAddr::from_str("2a02:ffc0::").unwrap()) 206 | .unwrap(); 207 | let continent = result.continent.unwrap(); 208 | assert_eq!(continent.geoname_id, Some(6255148)); 209 | assert_eq!(continent.code, Some("EU")); 210 | let names = continent.names.unwrap(); 211 | assert_eq!(names.get("en"), Some("Europe")); 212 | assert_eq!(names.get("zh-CN"), Some("欧洲")); 213 | 214 | let country = result.country.unwrap(); 215 | assert_eq!(country.geoname_id, Some(2411586)); 216 | assert_eq!(country.iso_code, Some("GI")); 217 | let names = country.names.unwrap(); 218 | assert_eq!(names.get("en"), Some("Gibraltar")); 219 | assert_eq!(names.get("ja"), Some("ジブラルタル")); 220 | assert_eq!(country.is_in_european_union, None); 221 | 222 | let registered_country = result.registered_country.unwrap(); 223 | assert_eq!(registered_country.geoname_id, Some(2411586)); 224 | 225 | assert!(result.represented_country.is_none()); 226 | 227 | assert!(result.traits.is_none()); 228 | } 229 | } 230 | 231 | #[test] 232 | fn test_domain() { 233 | let buffer = std::fs::read("./testdata/GeoIP2-Domain-Test.mmdb").unwrap(); 234 | let reader = Reader::::from_bytes(&buffer).unwrap(); 235 | { 236 | let result = reader 237 | .lookup(IpAddr::from_str("1.2.0.0").unwrap()) 238 | .unwrap() 239 | .domain 240 | .unwrap(); 241 | assert_eq!(result, "maxmind.com"); 242 | } 243 | { 244 | let result = reader 245 | .lookup(IpAddr::from_str("186.30.236.0").unwrap()) 246 | .unwrap() 247 | .domain 248 | .unwrap(); 249 | assert_eq!(result, "replaced.com"); 250 | } 251 | } 252 | 253 | #[test] 254 | fn test_isp() { 255 | let buffer = std::fs::read("./testdata/GeoIP2-ISP-Test.mmdb").unwrap(); 256 | let reader = Reader::::from_bytes(&buffer).unwrap(); 257 | { 258 | let result = reader 259 | .lookup(IpAddr::from_str("1.128.0.0").unwrap()) 260 | .unwrap(); 261 | assert_eq!(result.autonomous_system_number, Some(1221)); 262 | assert_eq!( 263 | result.autonomous_system_organization, 264 | Some("Telstra Pty Ltd") 265 | ); 266 | assert_eq!(result.isp, Some("Telstra Internet")); 267 | assert_eq!(result.organization, Some("Telstra Internet")); 268 | assert_eq!( 269 | result.autonomous_system_organization, 270 | Some("Telstra Pty Ltd") 271 | ); 272 | } 273 | { 274 | let result = reader.lookup(IpAddr::from_str("4.0.0.0").unwrap()).unwrap(); 275 | assert_eq!(result.autonomous_system_number, None); 276 | assert_eq!(result.autonomous_system_organization, None); 277 | assert_eq!(result.isp, Some("Level 3 Communications")); 278 | assert_eq!(result.organization, Some("Level 3 Communications")); 279 | } 280 | } 281 | 282 | #[test] 283 | fn test_asn() { 284 | let buffer = std::fs::read("./testdata/GeoLite2-ASN-Test.mmdb").unwrap(); 285 | let reader = Reader::::from_bytes(&buffer).unwrap(); 286 | { 287 | let result = reader 288 | .lookup(IpAddr::from_str("1.128.0.0").unwrap()) 289 | .unwrap(); 290 | assert_eq!(result.autonomous_system_number, Some(1221)); 291 | assert_eq!( 292 | result.autonomous_system_organization, 293 | Some("Telstra Pty Ltd") 294 | ); 295 | } 296 | { 297 | let result = reader 298 | .lookup(IpAddr::from_str("2600:6000::").unwrap()) 299 | .unwrap(); 300 | assert_eq!(result.autonomous_system_number, Some(237)); 301 | assert_eq!( 302 | result.autonomous_system_organization, 303 | Some("Merit Network Inc.") 304 | ); 305 | } 306 | } 307 | 308 | #[test] 309 | fn test_metadata() { 310 | let buffer = std::fs::read("./testdata/GeoLite2-ASN-Test.mmdb").unwrap(); 311 | let reader = Reader::::from_bytes(&buffer).unwrap(); 312 | let metadata = reader.get_metadata(); 313 | assert_eq!(metadata.binary_format_major_version, 2); 314 | assert_eq!(metadata.binary_format_minor_version, 0); 315 | assert_eq!(metadata.node_count, 1304); 316 | assert_eq!(metadata.record_size, 28); 317 | assert_eq!(metadata.ip_version, 6); 318 | assert_eq!(metadata.database_type, "GeoLite2-ASN"); 319 | assert_eq!(metadata.languages, vec!["en"]); 320 | assert_eq!(metadata.build_epoch, 1609263880); 321 | } 322 | } 323 | --------------------------------------------------------------------------------