ip_check/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
pub mod ip_lookup {

    use std::net::Ipv4Addr;
    use std::str::FromStr;
    use std::cmp::Ordering;
    use std::error::Error;
    use csv::Reader;


    #[derive(Debug, Clone)]
    pub struct IpRange {
        start: u32,
        end: u32,
        pub country: String,
        pub region: String,
        pub city: String,
    }

    #[derive(Debug)]
    pub struct Looker {
        pub file_path: String,
        pub ip_ranges: Vec<IpRange>,
    }

    pub trait IpLookup {
        fn look_up(&self, ip: &str) -> Option<IpRange>;
    }

    impl Looker {
        pub fn new(file_path: String) -> Self {
            let mut rdr = match Reader::from_path(&file_path) {
                Err(e) => {
                    panic!("Error: {}", e);
                },
                Ok(rdr) => {
                    rdr
                }
            };
            let mut ip_ranges = Vec::new();
            
            for result in rdr.records() {
                let record = result.unwrap();
                let start: u32 = record[0].parse().unwrap();
                let end: u32 = record[1].parse().unwrap();
                let country = record[2].to_string();
                let region = record[4].to_string();
                let city = record[5].to_string();
                
                ip_ranges.push(IpRange { start, end, country, region, city });
            }

            Looker {
                file_path,
                ip_ranges,
            }
        }

    }

    impl IpLookup for Looker {
        fn look_up(&self, ip: &str) -> Option<IpRange> {
            let ip_decimal_to_use = match ip_to_decimal(ip) {
                Err(e) => {
                    println!("Error: {}", e);
                    return None;
                },
                Ok(ip_decimal) => {
                    ip_decimal
                }
            };
            let ip_ranges_to_use = &self.ip_ranges;
            
            match find_ip_range(ip_decimal_to_use, &ip_ranges_to_use[..]) {
                Some(range) => {
                    println!("IP is in range: {:?}", range);
                    Some(range)
                },
                None => {
                    println!("IP not found in any range");
                    None
                }
            }
        }
    }

    fn read_ip_ranges(file_path: &str) -> Result<Vec<IpRange>, Box<dyn Error>> {
        let mut rdr = Reader::from_path(file_path)?;
        let mut ip_ranges = Vec::new();
        
        for result in rdr.records() {
            let record = result?;
            let start: u32 = record[0].parse()?;
            let end: u32 = record[1].parse()?;
            let country = record[2].to_string();
            let region = record[4].to_string();
            let city = record[5].to_string();
            
            ip_ranges.push(IpRange { start, end, country, region, city });
        }

        Ok(ip_ranges)
    }

    fn find_ip_range(ip: u32, ranges: &[IpRange]) -> Option<IpRange> {
        ranges.binary_search_by(|range| {
            if ip < range.start {
                Ordering::Greater // Search the left side
            } else if ip > range.end {
                Ordering::Less // Search the right side
            } else {
                Ordering::Equal // IP is within this range
            }
        }).ok().map(|index| ranges[index].clone())
    }

    fn ip_to_decimal(ip: &str) -> Result<u32,String> {
        let ip = Ipv4Addr::from_str(ip);
        if ip.is_err() {
            return Err("Invalid IP address".into());
        }
        let ip = ip.unwrap();
        let octets = ip.octets();
        let decimal = (octets[0] as u32) << 24 
            | (octets[1] as u32) << 16 
            | (octets[2] as u32) << 8 
            | octets[3] as u32;
        Ok(decimal)
    }

    pub fn look_up(ip: &str, file_path: &str) -> Option<IpRange> {
        let ip_decimal_to_use = match ip_to_decimal(ip) {
            Err(e) => {
                println!("Error: {}", e);
                return None;
            },
            Ok(ip_decimal) => {
                ip_decimal
            }
        };
         let ip_ranges_to_use = match read_ip_ranges(file_path) {
            Err(e) => {
                println!("Error: {}", e);
                return None;
            },
            Ok(ip_ranges) => {
                ip_ranges
            }
        };
        
        match find_ip_range(ip_decimal_to_use, &ip_ranges_to_use[..]) {
            Some(range) => {
                println!("IP is in range: {:?}", range);
                Some(range)
            },
            None => {
                println!("IP not found in any range");
                None
            }
        }
    }
}
pub use crate::ip_lookup::{look_up, Looker, IpLookup};