ipcap/
utils.rs

1use crate::geo_ip_reader::Record;
2use std::collections::HashMap;
3use std::net::{Ipv4Addr, Ipv6Addr};
4
5/// Helper function to create a single-level hashmap.
6///
7/// This function takes a single value and wraps it in a hashmap with a default key "default".
8///
9/// # Arguments
10///
11/// * `value` - The value to be stored in the hashmap.
12///
13/// # Returns
14///
15/// (`HashMap<&'static str, &'static str>`): A map with a single entry.
16///
17/// # Examples
18///
19/// ```
20/// use ipcap::utils::single_level;
21/// use std::collections::HashMap;
22///
23/// let map: HashMap<&'static str, &'static str> = single_level("example_value");
24/// assert_eq!(map.get("default"), Some(&"example_value"));
25/// assert_eq!(map.len(), 1);
26/// ```
27pub fn single_level(value: &'static str) -> HashMap<&'static str, &'static str> {
28    let mut map = HashMap::new();
29    map.insert("default", value);
30    map
31}
32
33/// Helper function to create a multi-level hashmap.
34///
35/// This function takes a vector of key-value pairs and converts them into a hashmap.
36///
37/// # Arguments
38///
39/// * `entries` - A vector of key-value pairs to be stored in the hashmap.
40///
41/// # Returns
42///
43/// (`HashMap<&'static str, &'static str>`): A map containing the provided key-value pairs.
44///
45/// # Examples
46///
47/// ```
48/// use ipcap::utils::multi_level;
49/// use std::collections::HashMap;
50///
51/// let entries = vec![
52///     ("key1", "value1"),
53///     ("key2", "value2"),
54/// ];
55///
56/// let map: HashMap<&'static str, &'static str> = multi_level(entries);
57/// assert_eq!(map.get("key1"), Some(&"value1"));
58/// assert_eq!(map.get("key2"), Some(&"value2"));
59/// assert_eq!(map.len(), 2);
60/// ```
61pub fn multi_level(
62    entries: Vec<(&'static str, &'static str)>,
63) -> HashMap<&'static str, &'static str> {
64    entries.into_iter().collect()
65}
66
67/// Converts an IP address in string format to a 128-bit unsigned integer representation.
68///
69/// This function takes a string representing an IP address and converts it into a 128-bit
70/// unsigned integer. It supports both IPv4 and IPv6 addresses. The result is the numeric
71/// representation of the IP address.
72///
73/// # Arguments
74///
75/// * `ip` - A string slice containing the IP address.
76///
77/// # Returns
78///
79/// (`u128`): A 128-bit unsigned integer representation of the IP address.
80///
81/// # Panics
82///
83/// This function will panic if the input string does not represent a valid IPv4 or IPv6 address.
84///
85/// # Examples
86///
87/// ```
88/// use ipcap::utils::ip_to_number;
89///
90/// let ipv4_address = "1.32.0.0";
91/// let ipv6_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
92///
93/// let ipv4_numeric = ip_to_number(ipv4_address);
94/// let ipv6_numeric = ip_to_number(ipv6_address);
95///
96/// assert_eq!(ipv4_numeric, 18874368);
97/// assert_eq!(ipv6_numeric, 42540766411283223938465490629124161536);
98/// ```
99pub fn ip_to_number(ip: &str) -> u128 {
100    match ip.parse::<Ipv4Addr>() {
101        Ok(ipv4_addr) => {
102            // IPv4 case
103            let ipv4_u32: u32 = u32::from(ipv4_addr);
104            u128::from(ipv4_u32)
105        }
106        Err(_) => {
107            // Not an IPv4 address, try IPv6
108            match ip.parse::<Ipv6Addr>() {
109                Ok(ipv6_addr) => {
110                    // IPv6 case
111                    let segments = ipv6_addr.segments();
112                    (u128::from(segments[0]) << 112)
113                        | (u128::from(segments[1]) << 96)
114                        | (u128::from(segments[2]) << 64)
115                        | u128::from(segments[3])
116                }
117                Err(_) => {
118                    // Invalid IP address
119                    panic!("Invalid IP address: {}", ip);
120                }
121            }
122        }
123    }
124}
125
126/// Reads null-terminated string data from the given buffer starting at the specified position.
127///
128/// # Arguments
129///
130/// * `buffer` - The buffer containing the string data.
131/// * `pos` - The starting position to read the string from.
132///
133/// # Returns
134///
135/// A tuple containing:
136/// - The updated position after reading the string.
137/// - An optional string representing the data read. `None` if no valid string is found.
138///
139/// # Examples
140///
141/// ```rust
142/// use ipcap::utils::read_data;
143///
144/// let buffer = b"Hello\0World";
145/// let pos = 0;
146/// let (new_pos, data) = read_data(buffer, pos);
147/// assert_eq!(new_pos, 5);
148/// assert_eq!(data, Some("Hello".into()));
149/// ```
150pub fn read_data(buffer: &[u8], pos: usize) -> (usize, Option<Box<str>>) {
151    let mut cur = pos;
152    while buffer[cur] != 0 {
153        cur += 1;
154    }
155    let data = if cur > pos {
156        Some(
157            String::from_utf8_lossy(&buffer[pos..cur])
158                .to_string()
159                .into_boxed_str(),
160        )
161    } else {
162        None
163    };
164    (cur, data)
165}
166
167/// Pretty prints the fields of a Record struct by sorting them alphabetically and formatting the output.
168///
169/// # Arguments
170///
171/// * `record` - A reference to a Record struct.
172///
173/// # Example
174///
175/// ```rust
176/// use ipcap::utils::pretty_print_dict;
177/// use ipcap::geo_ip_reader::Record;
178/// use ipcap::countries::Country;
179/// use ipcap::designated_market_area::DesignatedMarketArea;
180///
181/// let record = Record {
182///     dma: Some(DesignatedMarketArea(80700)),
183///     postal_code: Some("94040".into()),
184///     country: Country::UnitedStates,
185///     region_code: Some("CA".into()),
186///     city: Some("Mountain View".into()),
187///     latitude: 37.3845,
188///     longitude: -122.0881,
189///     time_zone: "America/Los_Angeles",
190/// };
191///
192/// pretty_print_dict(record);
193/// ```
194///
195/// Output:
196///
197/// ```sh
198/// {
199///     "area_code": "650",
200///     "city": "Mountain View",
201///     "continent": "NA",
202///     "country_code": "US",
203///     "country_code3": "USA",
204///     "country_name": "United States",
205///     "dma_code": "807",
206///     "latitude": "37.3845",
207///     "longitude": "-122.0881",
208///     "metro_code": "San Francisco, CA",
209///     "postal_code": "94040",
210///     "region_code": "CA",
211///     "time_zone": "America/Los_Angeles",
212/// }
213/// ```
214pub fn pretty_print_dict(record: Record) {
215    let data: Vec<(&str, Option<String>)> = vec![
216        ("dma_code", record.dma.map(|d| d.dma_code().to_string())),
217        ("area_code", record.dma.map(|d| d.area_code().to_string())),
218        ("metro_code", record.dma.map(|c| c.to_string())),
219        (
220            "postal_code",
221            record.postal_code.as_ref().map(|d| d.to_string()),
222        ),
223        (
224            "country_code",
225            Some(record.country.alphabetic_code_2().to_string()),
226        ),
227        (
228            "country_code3",
229            Some(record.country.alphabetic_code_3().to_string()),
230        ),
231        ("country_name", Some(record.country.to_string())),
232        (
233            "continent",
234            record.country.continent().map(|c| c.to_string()),
235        ),
236        ("region_code", record.region_code.map(|d| d.to_string())),
237        ("city", record.city.map(|d| d.to_string())),
238        ("latitude", Some(record.latitude.to_string())),
239        ("longitude", Some(record.longitude.to_string())),
240        ("time_zone", Some(record.time_zone.to_string())),
241    ];
242
243    let mut sorted_data = data.clone();
244    sorted_data.sort_by(|a, b| a.0.cmp(b.0));
245
246    println!("{{");
247
248    for (key, value) in sorted_data {
249        print!("    \"\u{1b}[1;32m{}\": ", key); // Green color for keys
250        match value {
251            Some(v) => print!("\u{1b}[1;37m\"{}\"\u{1b}[0m,", v), // Silver color for values
252            None => print!("\u{1b}[1;30mnull\u{1b}[0m,"),         // Gray color for null values
253        }
254        println!();
255    }
256
257    println!("}}");
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use std::collections::HashMap;
264
265    #[test]
266    fn test_single_level() {
267        let map: HashMap<&'static str, &'static str> = single_level("example_value");
268        assert_eq!(map.get("default"), Some(&"example_value"));
269        assert_eq!(map.len(), 1);
270    }
271
272    #[test]
273    fn test_multi_level() {
274        let entries = vec![("key1", "value1"), ("key2", "value2")];
275        let map: HashMap<&'static str, &'static str> = multi_level(entries);
276        assert_eq!(map.get("key1"), Some(&"value1"));
277        assert_eq!(map.get("key2"), Some(&"value2"));
278        assert_eq!(map.len(), 2);
279    }
280
281    #[test]
282    fn test_ip_to_number_ipv4() {
283        let ipv4_address = "192.168.1.1";
284        let result = ip_to_number(ipv4_address);
285        assert_eq!(result, 3232235777);
286    }
287
288    #[test]
289    fn test_ip_to_number_ipv6() {
290        // Test with a valid IPv6 address
291        let ipv6_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
292        let result = ip_to_number(ipv6_address);
293        assert_eq!(result, 42540766411283223938465490629124161536);
294    }
295
296    #[test]
297    #[should_panic(expected = "Invalid IP address")]
298    fn test_ip_to_number_invalid() {
299        let invalid_address = "invalid_ip";
300        ip_to_number(invalid_address);
301    }
302
303    #[test]
304    fn test_read_data_with_valid_string() {
305        let buffer = b"Hello\0World";
306        let pos = 0;
307        let (new_pos, data) = read_data(buffer, pos);
308        assert_eq!(new_pos, 5);
309        assert_eq!(data, Some("Hello".into()));
310    }
311
312    #[test]
313    fn test_read_data_with_empty_string() {
314        let buffer = b"\0World";
315        let pos = 0;
316        let (new_pos, data) = read_data(buffer, pos);
317        assert_eq!(new_pos, 0);
318        assert_eq!(data, None);
319    }
320
321    #[test]
322    #[should_panic(expected = "index out of bounds: the len is 10 but the index is 10")]
323    fn test_read_data_with_no_null_terminator() {
324        let buffer = b"HelloWorld";
325        let pos = 0;
326        let (new_pos, data) = read_data(buffer, pos);
327        assert_eq!(new_pos, buffer.len());
328        assert_eq!(data, None);
329    }
330}
331
332#[macro_export]
333macro_rules! codegen {
334    ($name: expr) => {
335        include!(concat!(env!("OUT_DIR"), "/", $name))
336    };
337
338    (statement; $name: expr) => {
339        include!(concat!(env!("OUT_DIR"), "/", $name));
340    };
341}