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}