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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};

/// Helper function to create a single-level hashmap.
///
/// This function takes a single value and wraps it in a hashmap with a default key "default".
///
/// # Arguments
///
/// * `value` - The value to be stored in the hashmap.
///
/// # Returns
///
/// (`HashMap<&'static str, &'static str>`): A map with a single entry.
///
/// # Examples
///
/// ```
/// use ipcap::utils::single_level;
/// use std::collections::HashMap;
///
/// let map: HashMap<&'static str, &'static str> = single_level("example_value");
/// assert_eq!(map.get("default"), Some(&"example_value"));
/// assert_eq!(map.len(), 1);
/// ```
pub fn single_level(value: &'static str) -> HashMap<&'static str, &'static str> {
    let mut map = HashMap::new();
    map.insert("default", value);
    map
}

/// Helper function to create a multi-level hashmap.
///
/// This function takes a vector of key-value pairs and converts them into a hashmap.
///
/// # Arguments
///
/// * `entries` - A vector of key-value pairs to be stored in the hashmap.
///
/// # Returns
///
/// (`HashMap<&'static str, &'static str>`): A map containing the provided key-value pairs.
///
/// # Examples
///
/// ```
/// use ipcap::utils::multi_level;
/// use std::collections::HashMap;
///
/// let entries = vec![
///     ("key1", "value1"),
///     ("key2", "value2"),
/// ];
///
/// let map: HashMap<&'static str, &'static str> = multi_level(entries);
/// assert_eq!(map.get("key1"), Some(&"value1"));
/// assert_eq!(map.get("key2"), Some(&"value2"));
/// assert_eq!(map.len(), 2);
/// ```
pub fn multi_level(
    entries: Vec<(&'static str, &'static str)>,
) -> HashMap<&'static str, &'static str> {
    entries.into_iter().collect()
}

/// Converts an IP address in string format to a 128-bit unsigned integer representation.
///
/// This function takes a string representing an IP address and converts it into a 128-bit
/// unsigned integer. It supports both IPv4 and IPv6 addresses. The result is the numeric
/// representation of the IP address.
///
/// # Arguments
///
/// * `ip` - A string slice containing the IP address.
///
/// # Returns
///
/// (`u128`): A 128-bit unsigned integer representation of the IP address.
///
/// # Panics
///
/// This function will panic if the input string does not represent a valid IPv4 or IPv6 address.
///
/// # Examples
///
/// ```
/// use ipcap::utils::ip_to_number;
///
/// let ipv4_address = "1.32.0.0";
/// let ipv6_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
///
/// let ipv4_numeric = ip_to_number(ipv4_address);
/// let ipv6_numeric = ip_to_number(ipv6_address);
///
/// assert_eq!(ipv4_numeric, 18874368);
/// assert_eq!(ipv6_numeric, 42540766411283223938465490629124161536);
/// ```
pub fn ip_to_number(ip: &str) -> u128 {
    match ip.parse::<Ipv4Addr>() {
        Ok(ipv4_addr) => {
            // IPv4 case
            let ipv4_u32: u32 = u32::from(ipv4_addr);
            u128::from(ipv4_u32)
        }
        Err(_) => {
            // Not an IPv4 address, try IPv6
            match ip.parse::<Ipv6Addr>() {
                Ok(ipv6_addr) => {
                    // IPv6 case
                    let segments = ipv6_addr.segments();
                    (u128::from(segments[0]) << 112)
                        | (u128::from(segments[1]) << 96)
                        | (u128::from(segments[2]) << 64)
                        | u128::from(segments[3])
                }
                Err(_) => {
                    // Invalid IP address
                    panic!("Invalid IP address: {}", ip);
                }
            }
        }
    }
}

/// Reads null-terminated string data from the given buffer starting at the specified position.
///
/// # Arguments
///
/// * `buffer` - The buffer containing the string data.
/// * `pos` - The starting position to read the string from.
///
/// # Returns
///
/// A tuple containing:
/// - The updated position after reading the string.
/// - An optional string representing the data read. `None` if no valid string is found.
///
/// # Examples
///
/// ```
/// use ipcap::utils::read_data;
///
/// let buffer = b"Hello\0World";
/// let pos = 0;
/// let (new_pos, data) = read_data(buffer, pos);
/// assert_eq!(new_pos, 5);
/// assert_eq!(data, Some("Hello".to_string()));
/// ```
pub fn read_data(buffer: &[u8], pos: usize) -> (usize, Option<String>) {
    let mut cur = pos;
    while buffer[cur] != 0 {
        cur += 1;
    }
    let data = if cur > pos {
        Some(String::from_utf8_lossy(&buffer[pos..cur]).to_string())
    } else {
        None
    };
    (cur, data)
}

/// Pretty prints a HashMap by sorting keys alphabetically and formatting the output.
///
/// # Arguments
///
/// * `data` - A reference to a HashMap with keys as string references and values as optional strings.
///
/// # Example
///
/// ```rust
/// use std::collections::HashMap;
/// use ipcap::utils::pretty_print_dict;
///
/// let mut data = HashMap::new();
/// data.insert("time_zone", Some("America/Los_Angeles".to_string()));
/// data.insert("dma_code", Some("807".to_string()));
/// data.insert("continent", Some("NA".to_string()));
/// data.insert("longitude", Some("-122.0881".to_string()));
/// data.insert("area_code", Some("650".to_string()));
/// data.insert("country_code", Some("US".to_string()));
/// data.insert("postal_code", Some("94040".to_string()));
/// data.insert("country_code3", Some("USA".to_string()));
/// data.insert("country_name", Some("United States".to_string()));
/// data.insert("metro_code", Some("San Francisco, CA".to_string()));
/// data.insert("region_code", Some("CA".to_string()));
/// data.insert("city", Some("Mountain View".to_string()));
/// data.insert("latitude", Some("37.3845".to_string()));
///
/// pretty_print_dict(&data);
/// ```
///
/// Output:
///
/// ```sh
/// {
///     "area_code": "650",
///     "city": "Mountain View",
///     "continent": "NA",
///     "country_code": "US",
///     "country_code3": "USA",
///     "country_name": "United States",
///     "dma_code": "807",
///     "latitude": "37.3845",
///     "longitude": "-122.0881",
///     "metro_code": "San Francisco, CA",
///     "postal_code": "94040",
///     "region_code": "CA",
///     "time_zone": "America/Los_Angeles",
/// }
/// ```
pub fn pretty_print_dict(data: &HashMap<&str, Option<String>>) {
    let mut sorted_keys: Vec<_> = data.keys().cloned().collect();
    sorted_keys.sort();

    println!("{{");

    for key in sorted_keys {
        print!("    \"\u{1b}[1;32m{}\": ", key); // Green color for keys
        if let Some(value) = data.get(key) {
            match value {
                Some(v) => print!("\u{1b}[1;37m\"{}\"\u{1b}[0m,", v), // Silver color for values
                None => print!("\u{1b}[1;30mnull\u{1b}[0m,"),         // Gray color for null values
            }
        }
        println!();
    }

    println!("}}");
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn test_single_level() {
        let map: HashMap<&'static str, &'static str> = single_level("example_value");
        assert_eq!(map.get("default"), Some(&"example_value"));
        assert_eq!(map.len(), 1);
    }

    #[test]
    fn test_multi_level() {
        let entries = vec![("key1", "value1"), ("key2", "value2")];
        let map: HashMap<&'static str, &'static str> = multi_level(entries);
        assert_eq!(map.get("key1"), Some(&"value1"));
        assert_eq!(map.get("key2"), Some(&"value2"));
        assert_eq!(map.len(), 2);
    }

    #[test]
    fn test_ip_to_number_ipv4() {
        let ipv4_address = "192.168.1.1";
        let result = ip_to_number(ipv4_address);
        assert_eq!(result, 3232235777);
    }

    #[test]
    fn test_ip_to_number_ipv6() {
        // Test with a valid IPv6 address
        let ipv6_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
        let result = ip_to_number(ipv6_address);
        assert_eq!(result, 42540766411283223938465490629124161536);
    }

    #[test]
    #[should_panic(expected = "Invalid IP address")]
    fn test_ip_to_number_invalid() {
        let invalid_address = "invalid_ip";
        ip_to_number(invalid_address);
    }

    #[test]
    fn test_read_data_with_valid_string() {
        let buffer = b"Hello\0World";
        let pos = 0;
        let (new_pos, data) = read_data(buffer, pos);
        assert_eq!(new_pos, 5);
        assert_eq!(data, Some("Hello".to_string()));
    }

    #[test]
    fn test_read_data_with_empty_string() {
        let buffer = b"\0World";
        let pos = 0;
        let (new_pos, data) = read_data(buffer, pos);
        assert_eq!(new_pos, 0);
        assert_eq!(data, None);
    }

    #[test]
    #[should_panic(expected = "index out of bounds: the len is 10 but the index is 10")]
    fn test_read_data_with_no_null_terminator() {
        let buffer = b"HelloWorld";
        let pos = 0;
        let (new_pos, data) = read_data(buffer, pos);
        assert_eq!(new_pos, buffer.len());
        assert_eq!(data, None);
    }
}