diff --git a/binding/rust/Cargo.toml b/binding/rust/Cargo.toml index 1ec6963..d455ea1 100644 --- a/binding/rust/Cargo.toml +++ b/binding/rust/Cargo.toml @@ -1,8 +1,2 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] +[workspace] +members = ["bin", "search"] diff --git a/binding/rust/bin/Cargo.toml b/binding/rust/bin/Cargo.toml new file mode 100644 index 0000000..57c59df --- /dev/null +++ b/binding/rust/bin/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ip2region" +default-run = "ip2region" +version = "0.1.0" +edition = "2021" +rust-version = "1.66.0" +description = "the rust binding for ip2region" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +search = { path = "../search" } +clap = { version = "4.0" } diff --git a/binding/rust/bin/src/cmd.rs b/binding/rust/bin/src/cmd.rs new file mode 100644 index 0000000..64e5ee2 --- /dev/null +++ b/binding/rust/bin/src/cmd.rs @@ -0,0 +1,9 @@ +use clap::{arg, ArgMatches, Command}; + +pub fn get_matches() -> ArgMatches { + Command::new("ip2region") + .version("0.1") + .about("ip2region bin program") + .arg(arg!(--xdb "the xdb filepath, you can set this field like ../data/ip2region.xdb").required(true)) + .get_matches() +} diff --git a/binding/rust/bin/src/main.rs b/binding/rust/bin/src/main.rs new file mode 100644 index 0000000..54d63db --- /dev/null +++ b/binding/rust/bin/src/main.rs @@ -0,0 +1,9 @@ +mod cmd; + +fn main() { + let matches = cmd::get_matches(); + let xdb_filepath = matches.get_one::("xdb").unwrap(); + println!("{xdb_filepath}----"); + // let result = search::search_by_ip("1.2.165.128"); + // println!("{:?}", result); +} diff --git a/binding/rust/search/Cargo.toml b/binding/rust/search/Cargo.toml new file mode 100644 index 0000000..e6b096e --- /dev/null +++ b/binding/rust/search/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "search" +version = "0.1.0" +edition = "2021" +rust-version = "1.66.0" +description = "the rust binding for ip2region" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +once_cell = "1.16" + +[dev-dependencies] +criterion = "0.3" +rand = "0.8" + +#[[bench]] +#name = "search" +#harness = false diff --git a/binding/rust/search/benches/search.rs b/binding/rust/search/benches/search.rs new file mode 100644 index 0000000..a7270cb --- /dev/null +++ b/binding/rust/search/benches/search.rs @@ -0,0 +1,13 @@ +// use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId}; +// use rand; +// +// fn ip_search_benchmark(c: &mut Criterion) { +// let searcher = search::Searcher::new("../../../data/ip2region.xdb").unwrap(); +// c.bench_with_input(BenchmarkId::new("searcher", searcher), +// &searcher, |b, &seacher| { +// b.iter(|| seacher.search_by_ip(black_box(rand::random::()))); +// }); +// } +// +// criterion_group!(benches, ip_search_benchmark); +// criterion_main!(benches); diff --git a/binding/rust/src/ip_value.rs b/binding/rust/search/src/ip_value.rs similarity index 100% rename from binding/rust/src/ip_value.rs rename to binding/rust/search/src/ip_value.rs diff --git a/binding/rust/search/src/lib.rs b/binding/rust/search/src/lib.rs new file mode 100644 index 0000000..7591380 --- /dev/null +++ b/binding/rust/search/src/lib.rs @@ -0,0 +1,138 @@ +mod ip_value; + +use std::error::Error; +use std::fs::File; +use std::io::Read; +use std::fmt; +use std::fmt::Formatter; + +use once_cell::sync::OnceCell; + +use ip_value::ToUIntIP; + +const HEADER_INFO_LENGTH: u32 = 256; +// const VECTOR_INDEX_ROWS: u32 = 256; +const VECTOR_INDEX_COLS: u32 = 256; +const VECTOR_INDEX_SIZE: u32 = 8; +const SEGMENT_INDEX_SIZE: usize = 14; + +const DEFAULT_XDB_FILEPATH: &str = "../../../data/ip2region.xdb"; + +pub struct Searcher { + pub buffer: Vec, +} + +fn global_searcher() -> &'static Searcher { + static SEARCHER: OnceCell = OnceCell::new(); + SEARCHER.get_or_init(|| { + Searcher::new(DEFAULT_XDB_FILEPATH).unwrap() + }) +} + +impl fmt::Display for Searcher { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "searcher_with_len {}", self.buffer.len()) + } +} + +pub fn search_by_ip(ip: T) -> Result> + where + T: ToUIntIP, +{ + let changed_value = ip.to_u32_ip()?; + search_by_ip_u32(changed_value) +} + +pub fn search_by_ip_u32(ip: u32) -> Result> { + let il0 = (ip >> 24) & 0xFF; + let il1 = (ip >> 16) & 0xFF; + let idx = VECTOR_INDEX_SIZE * (il0 * VECTOR_INDEX_COLS + il1); + + let start_point = (HEADER_INFO_LENGTH + idx) as usize; + let buffer = &global_searcher().buffer; + let start_ptr = get_u32(buffer, start_point); + let end_ptr = get_u32(buffer, start_point + 4); + let mut left: usize = 0; + let mut right: usize = ((end_ptr - start_ptr) as usize) / SEGMENT_INDEX_SIZE; + while left <= right { + let mid = (left + right) >> 1; + let offset = (start_ptr as usize) + mid * SEGMENT_INDEX_SIZE; + let buffer_ip_value = buffer_value(offset, SEGMENT_INDEX_SIZE); + let start_ip = get_u32(buffer_ip_value, 0); + if ip < start_ip { + right = mid - 1; + } else if ip > get_u32(buffer_ip_value, 4) { + left = mid + 1; + } else { + let length = (buffer_ip_value[8] as usize & 0x000000FF) + | (buffer_ip_value[9] as usize & 0x0000FF00); + + let offset = get_u32(buffer_ip_value, 10); + let result = buffer_value(offset as usize, length) + .iter() + .map(|x| x.to_owned()) + .collect::>(); + return Ok(String::from_utf8(result)?); + } + } + Err("not matched".into()) +} + +pub fn buffer_value(offset: usize, length: usize) -> &'static [u8] { + &global_searcher().buffer[offset..offset + length] +} + +impl Searcher { + pub fn new(filepath: &str) -> Result> { + let mut f = File::open(filepath)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + Ok(Self { buffer }) + } +} + +fn get_u32(bytes: &[u8], offset: usize) -> u32 { + (bytes[offset] as u32) & 0x000000FF + | ((bytes[offset + 1] as u32) << 8) & 0x0000FF00 + | ((bytes[offset + 2] as u32) << 16) & 0x00FF0000 + | ((bytes[offset + 3] as u32) << 24) & 0xFF000000 +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::Ipv4Addr; + use std::str::FromStr; + + const TEST_IP_FILEPATH: &str = "../../../data/ip.test.txt"; + + ///test all types find correct + #[test] + fn test_search_by_ip() { + search_by_ip("2.0.0.0").unwrap(); + search_by_ip("32").unwrap(); + search_by_ip(32).unwrap(); + search_by_ip(Ipv4Addr::from_str("1.1.1.1").unwrap()) + .unwrap(); + } + + /// test find ip correct use the file ip.test.txt in ../../data + #[test] + fn test_random_choose_ip() { + let mut file = File::open(TEST_IP_FILEPATH).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + for line in contents.split("\n") { + if !line.contains("|") { + continue; + } + let ip_test_line = line.splitn(3, "|").collect::>(); + let start_ip = Ipv4Addr::from_str(ip_test_line[0]).unwrap(); + let end_ip = Ipv4Addr::from_str(ip_test_line[1]).unwrap(); + for value in u32::from(start_ip)..u32::from(end_ip) + 1 { + let result = search_by_ip(value).unwrap(); + assert_eq!(result.as_str(), ip_test_line[2]) + } + } + } +} diff --git a/binding/rust/src/main.rs b/binding/rust/src/main.rs deleted file mode 100644 index 023c20c..0000000 --- a/binding/rust/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod ip_value; -mod search; - -fn main() { - println!(""); - // let filepath = "../../data/ip2region.xdb"; - // let searcher = lib::Searcher::new(filepath).expect("load file error"); - // let result = searcher.search_by_ip("1.2.165.128"); - // println!("{:?}", result); -} diff --git a/binding/rust/src/search.rs b/binding/rust/src/search.rs deleted file mode 100644 index e6564b5..0000000 --- a/binding/rust/src/search.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::error::Error; -use std::fs::File; -use std::io::Read; -use std::net::Ipv4Addr; -use std::str::FromStr; - -use crate::ip_value::ToUIntIP; - -const HEADER_INFO_LENGTH: u32 = 256; -// const VECTOR_INDEX_ROWS: u32 = 256; -const VECTOR_INDEX_COLS: u32 = 256; -const VECTOR_INDEX_SIZE: u32 = 8; -const SEGMENT_INDEX_SIZE: usize = 14; - -pub struct Searcher { - pub buffer: Vec, -} - -impl Searcher { - pub fn new(filepath: &'static str) -> Result> { - let mut f = File::open(filepath)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; - Ok(Self { buffer }) - } - - pub fn search_by_ip(&self, ip: T) -> Result> - where - T: ToUIntIP, - { - let changed_value = ip.to_u32_ip()?; - self.search_by_ip_u32(changed_value) - } - - pub fn search_by_ip_u32(&self, ip: u32) -> Result> { - let il0 = (ip >> 24) & 0xFF; - let il1 = (ip >> 16) & 0xFF; - let idx = VECTOR_INDEX_SIZE * (il0 * VECTOR_INDEX_COLS + il1); - - let start_point = (HEADER_INFO_LENGTH + idx) as usize; - let start_ptr = get_u32(&self.buffer, start_point); - let end_ptr = get_u32(&self.buffer, start_point + 4); - let mut left: usize = 0; - let mut right: usize = ((end_ptr - start_ptr) as usize) / SEGMENT_INDEX_SIZE; - while left <= right { - let mid = (left + right) >> 1; - let offset = (start_ptr as usize) + mid * SEGMENT_INDEX_SIZE; - let buffer_ip_value = self.buffer_value(offset, SEGMENT_INDEX_SIZE); - let start_ip = get_u32(buffer_ip_value, 0); - if ip < start_ip { - right = mid - 1; - } else if ip > get_u32(buffer_ip_value, 4) { - left = mid + 1; - } else { - let length = (buffer_ip_value[8] as usize & 0x000000FF) - | (buffer_ip_value[9] as usize & 0x0000FF00); - - let offset = get_u32(&buffer_ip_value, 10); - let result = self - .buffer_value(offset as usize, length) - .iter() - .map(|x| x.to_owned()) - .collect::>(); - return Ok(String::from_utf8(result)?); - } - } - Err("not matched".into()) - } - - pub fn buffer_value(&self, offset: usize, length: usize) -> &[u8] { - &self.buffer[offset..offset + length] - } -} - -fn get_u32(bytes: &[u8], offset: usize) -> u32 { - let result = (bytes[offset] as u32) & 0x000000FF - | ((bytes[offset + 1] as u32) << 8) & 0x0000FF00 - | ((bytes[offset + 2] as u32) << 16) & 0x00FF0000 - | ((bytes[offset + 3] as u32) << 24) & 0xFF000000; - result -} - -#[cfg(test)] -mod tests { - use super::*; - - ///test all types find correct - #[test] - fn test_search_by_ip() { - let searcher = Searcher::new(get_xdb_filepath()).expect("load file error"); - searcher.search_by_ip("2.0.0.0").unwrap(); - searcher.search_by_ip("32").unwrap(); - searcher.search_by_ip(32).unwrap(); - searcher - .search_by_ip(Ipv4Addr::from_str("1.1.1.1").unwrap()) - .unwrap(); - } - - fn get_xdb_filepath() -> &'static str { - "../../data/ip2region.xdb" - } - - /// test find ip correct use the file ip.test.txt in ../../data - #[test] - fn test_random_choose_ip() { - let searcher = Searcher::new(get_xdb_filepath()).unwrap(); - let mut file = File::open("../../data/ip.test.txt").unwrap(); - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - for line in contents.split("\n") { - if !line.contains("|") { - continue; - } - let ip_test_line = line.splitn(3, "|").collect::>(); - let start_ip = Ipv4Addr::from_str(ip_test_line[0]).unwrap(); - let end_ip = Ipv4Addr::from_str(ip_test_line[1]).unwrap(); - for value in u32::from(start_ip)..u32::from(end_ip) + 1 { - let result = searcher.search_by_ip(value).unwrap(); - assert_eq!(result.as_str(), ip_test_line[2]) - } - } - } -}