Skip to main content

packet_parser/owned/
mod.rs

1use serde::Serialize;
2use std::{
3    fmt::{Display, Formatter, Result},
4    net::IpAddr,
5};
6
7use crate::parse::data_link::vlan_tag::VlanTag;
8use crate::{IpType, PacketFlow};
9
10#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)]
11pub struct PacketFlowOwned {
12    #[serde(flatten)]
13    pub data_link: DataLinkOwned,
14    #[serde(flatten)]
15    pub internet: Option<InternetOwned>,
16    #[serde(flatten)]
17    pub transport: Option<TransportOwned>,
18    #[serde(flatten)]
19    pub application: Option<ApplicationOwned>,
20}
21
22#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)]
23pub struct DataLinkOwned {
24    pub destination_mac: String,
25    /// The source MAC address as a string.
26    pub source_mac: String,
27    /// The Ethertype of the packet, indicating the protocol in the payload.
28    pub ethertype: String,
29    pub vlan: Option<VlanTag>,
30}
31
32impl Display for DataLinkOwned {
33    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
34        write!(
35            f,
36            "\n    Destination MAC: {},\n    Source MAC: {},\n    Ethertype: {},\n    VLAN: ",
37            self.destination_mac, self.source_mac, self.ethertype,
38        )?;
39
40        match &self.vlan {
41            Some(vlan) => write!(f, "{vlan}")?,
42            None => write!(f, "None")?,
43        }
44
45        writeln!(f)
46    }
47}
48
49impl Display for InternetOwned {
50    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
51        // Pas d’allocation: on écrit directement.
52        write!(f, "\n    Source IP: ")?;
53        match &self.source_ip {
54            Some(ip) => write!(f, "{ip}")?,
55            None => write!(f, "None")?,
56        }
57
58        write!(f, ",\n    Destination IP: ")?;
59        match &self.destination_ip {
60            Some(ip) => write!(f, "{ip}")?,
61            None => write!(f, "None")?,
62        }
63
64        write!(f, ",\n    Protocol: {}\n", self.protocol)
65    }
66}
67
68impl Display for TransportOwned {
69    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
70        write!(f, "\n    Source Port: ")?;
71        match self.source_port {
72            Some(p) => write!(f, "{p}")?,
73            None => write!(f, "None")?,
74        }
75
76        write!(f, ",\n    Destination Port: ")?;
77        match self.destination_port {
78            Some(p) => write!(f, "{p}")?,
79            None => write!(f, "None")?,
80        }
81
82        write!(f, ",\n    Protocol: {}\n", self.protocol)
83    }
84}
85
86impl Display for ApplicationOwned {
87    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
88        write!(f, "\n    Protocol: {}\n", self.protocol)
89    }
90}
91
92#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)]
93pub struct InternetOwned {
94    pub source_ip: Option<IpAddr>,
95    pub ip_source_type: Option<IpType>,
96    pub destination_ip: Option<IpAddr>,
97    pub ip_destination_type: Option<IpType>,
98    pub protocol: String,
99}
100
101#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)]
102pub struct TransportOwned {
103    pub source_port: Option<u16>,
104    pub destination_port: Option<u16>,
105    pub protocol: String,
106}
107
108#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)]
109pub struct ApplicationOwned {
110    pub protocol: String,
111}
112
113impl<'a> From<PacketFlow<'a>> for PacketFlowOwned {
114    fn from(flow: PacketFlow<'a>) -> Self {
115        Self {
116            data_link: DataLinkOwned {
117                destination_mac: flow.data_link.destination_mac,
118                source_mac: flow.data_link.source_mac,
119                ethertype: flow.data_link.ethertype,
120                vlan: flow.data_link.vlan,
121            },
122            internet: flow.internet.map(|internet| InternetOwned {
123                source_ip: internet.source,
124                ip_source_type: internet.source_type,
125                destination_ip: internet.destination,
126                ip_destination_type: internet.destination_type,
127                protocol: internet.protocol_name,
128            }),
129            transport: flow.transport.map(|transport| TransportOwned {
130                source_port: transport.source_port,
131                destination_port: transport.destination_port,
132                protocol: transport.protocol.to_string(),
133            }),
134            application: flow.application.map(|application| ApplicationOwned {
135                protocol: application.application_protocol.to_string(),
136            }),
137        }
138    }
139}
140
141impl Display for PacketFlowOwned {
142    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
143        writeln!(f, "Packet Flow:")?;
144        writeln!(f, "  Data Link: {}", self.data_link)?;
145
146        if let Some(internet) = &self.internet {
147            writeln!(f, "  Internet: {internet}")?;
148        }
149        if let Some(transport) = &self.transport {
150            writeln!(f, "  Transport: {transport}")?;
151        }
152        if let Some(application) = &self.application {
153            writeln!(f, "  Application: {application}")?;
154        }
155
156        Ok(())
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use serde_json;
164    use std::collections::hash_map::DefaultHasher;
165    use std::hash::{Hash, Hasher};
166    use std::net::{IpAddr, Ipv4Addr};
167
168    fn sample_data_link_without_vlan() -> DataLinkOwned {
169        DataLinkOwned {
170            destination_mac: "AA:BB:CC:DD:EE:FF".to_string(),
171            source_mac: "11:22:33:44:55:66".to_string(),
172            ethertype: "IPv4".to_string(),
173            vlan: None,
174        }
175    }
176
177    fn sample_internet() -> InternetOwned {
178        InternetOwned {
179            source_ip: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))),
180            ip_source_type: None,
181            destination_ip: Some(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))),
182            ip_destination_type: None,
183            protocol: "TCP".to_string(),
184        }
185    }
186
187    fn sample_transport() -> TransportOwned {
188        TransportOwned {
189            source_port: Some(12345),
190            destination_port: Some(80),
191            protocol: "TCP".to_string(),
192        }
193    }
194
195    fn sample_application() -> ApplicationOwned {
196        ApplicationOwned {
197            protocol: "HTTP".to_string(),
198        }
199    }
200
201    fn sample_packet_flow_owned() -> PacketFlowOwned {
202        PacketFlowOwned {
203            data_link: sample_data_link_without_vlan(),
204            internet: Some(sample_internet()),
205            transport: Some(sample_transport()),
206            application: Some(sample_application()),
207        }
208    }
209
210    fn hash_of<T: Hash>(value: &T) -> u64 {
211        let mut hasher = DefaultHasher::new();
212        value.hash(&mut hasher);
213        hasher.finish()
214    }
215
216    #[test]
217    fn test_data_link_owned_display_without_vlan() {
218        let data_link = sample_data_link_without_vlan();
219
220        let expected = "\n    Destination MAC: AA:BB:CC:DD:EE:FF,\n    Source MAC: 11:22:33:44:55:66,\n    Ethertype: IPv4,\n    VLAN: None\n";
221        assert_eq!(data_link.to_string(), expected);
222    }
223
224    #[test]
225    fn test_internet_owned_display_with_ips() {
226        let internet = sample_internet();
227
228        let expected =
229            "\n    Source IP: 192.168.1.10,\n    Destination IP: 8.8.8.8,\n    Protocol: TCP\n";
230        assert_eq!(internet.to_string(), expected);
231    }
232
233    #[test]
234    fn test_internet_owned_display_with_none_ips() {
235        let internet = InternetOwned {
236            source_ip: None,
237            ip_source_type: None,
238            destination_ip: None,
239            ip_destination_type: None,
240            protocol: "UDP".to_string(),
241        };
242
243        let expected = "\n    Source IP: None,\n    Destination IP: None,\n    Protocol: UDP\n";
244        assert_eq!(internet.to_string(), expected);
245    }
246
247    #[test]
248    fn test_transport_owned_display_with_ports() {
249        let transport = sample_transport();
250
251        let expected = "\n    Source Port: 12345,\n    Destination Port: 80,\n    Protocol: TCP\n";
252        assert_eq!(transport.to_string(), expected);
253    }
254
255    #[test]
256    fn test_transport_owned_display_with_none_ports() {
257        let transport = TransportOwned {
258            source_port: None,
259            destination_port: None,
260            protocol: "ICMP".to_string(),
261        };
262
263        let expected =
264            "\n    Source Port: None,\n    Destination Port: None,\n    Protocol: ICMP\n";
265        assert_eq!(transport.to_string(), expected);
266    }
267
268    #[test]
269    fn test_application_owned_display() {
270        let application = sample_application();
271
272        let expected = "\n    Protocol: HTTP\n";
273        assert_eq!(application.to_string(), expected);
274    }
275
276    #[test]
277    fn test_packet_flow_owned_display_full() {
278        let flow = sample_packet_flow_owned();
279
280        let expected = concat!(
281            "Packet Flow:\n",
282            "  Data Link: \n",
283            "    Destination MAC: AA:BB:CC:DD:EE:FF,\n",
284            "    Source MAC: 11:22:33:44:55:66,\n",
285            "    Ethertype: IPv4,\n",
286            "    VLAN: None\n",
287            "\n",
288            "  Internet: \n",
289            "    Source IP: 192.168.1.10,\n",
290            "    Destination IP: 8.8.8.8,\n",
291            "    Protocol: TCP\n",
292            "\n",
293            "  Transport: \n",
294            "    Source Port: 12345,\n",
295            "    Destination Port: 80,\n",
296            "    Protocol: TCP\n",
297            "\n",
298            "  Application: \n",
299            "    Protocol: HTTP\n",
300            "\n"
301        );
302
303        assert_eq!(flow.to_string(), expected);
304    }
305
306    #[test]
307    fn test_packet_flow_owned_display_only_data_link() {
308        let flow = PacketFlowOwned {
309            data_link: sample_data_link_without_vlan(),
310            internet: None,
311            transport: None,
312            application: None,
313        };
314
315        let expected = concat!(
316            "Packet Flow:\n",
317            "  Data Link: \n",
318            "    Destination MAC: AA:BB:CC:DD:EE:FF,\n",
319            "    Source MAC: 11:22:33:44:55:66,\n",
320            "    Ethertype: IPv4,\n",
321            "    VLAN: None\n",
322            "\n"
323        );
324
325        assert_eq!(flow.to_string(), expected);
326    }
327
328    #[test]
329    fn test_packet_flow_owned_clone_and_eq() {
330        let flow = sample_packet_flow_owned();
331        let cloned = flow.clone();
332
333        assert_eq!(flow, cloned);
334    }
335
336    #[test]
337    fn test_packet_flow_owned_hash_stable_for_equal_values() {
338        let flow1 = sample_packet_flow_owned();
339        let flow2 = sample_packet_flow_owned();
340
341        assert_eq!(flow1, flow2);
342        assert_eq!(hash_of(&flow1), hash_of(&flow2));
343    }
344
345    #[test]
346    fn test_data_link_owned_hash_stable_for_equal_values() {
347        let dl1 = sample_data_link_without_vlan();
348        let dl2 = sample_data_link_without_vlan();
349
350        assert_eq!(dl1, dl2);
351        assert_eq!(hash_of(&dl1), hash_of(&dl2));
352    }
353
354    #[test]
355    fn test_packet_flow_owned_serialize() {
356        let flow = sample_packet_flow_owned();
357        let json = serde_json::to_string(&flow).unwrap();
358
359        assert!(json.contains("\"destination_mac\":\"AA:BB:CC:DD:EE:FF\""));
360        assert!(json.contains("\"source_mac\":\"11:22:33:44:55:66\""));
361        assert!(json.contains("\"ethertype\":\"IPv4\""));
362        assert!(json.contains("\"source_ip\":\"192.168.1.10\""));
363        assert!(json.contains("\"destination_ip\":\"8.8.8.8\""));
364        assert!(json.contains("\"source_port\":12345"));
365        assert!(json.contains("\"destination_port\":80"));
366        assert!(json.contains("\"protocol\":\"HTTP\"") || json.contains("\"protocol\":\"TCP\""));
367    }
368
369    #[test]
370    fn test_internet_owned_serialize_none_fields() {
371        let internet = InternetOwned {
372            source_ip: None,
373            ip_source_type: None,
374            destination_ip: None,
375            ip_destination_type: None,
376            protocol: "UDP".to_string(),
377        };
378
379        let json = serde_json::to_string(&internet).unwrap();
380
381        assert!(json.contains("\"source_ip\":null"));
382        assert!(json.contains("\"destination_ip\":null"));
383        assert!(json.contains("\"protocol\":\"UDP\""));
384    }
385}