huginn_net_db/
observable_tcp_signals_matching.rs

1use crate::db::TcpIndexKey;
2use crate::db_matching_trait::{DatabaseSignature, MatchQuality, ObservedFingerprint};
3use crate::observable_signals::TcpObservation;
4use crate::tcp::{self, IpVersion, PayloadSize};
5
6impl TcpObservation {
7    pub(crate) fn distance_olen(&self, other: &tcp::Signature) -> Option<u32> {
8        if self.olen == other.olen {
9            Some(tcp::TcpMatchQuality::High.as_score())
10        } else {
11            Some(tcp::TcpMatchQuality::Low.as_score())
12        }
13    }
14
15    pub(crate) fn distance_mss(&self, other: &tcp::Signature) -> Option<u32> {
16        if other.mss.is_none() || self.mss == other.mss {
17            Some(tcp::TcpMatchQuality::High.as_score())
18        } else {
19            Some(tcp::TcpMatchQuality::Low.as_score())
20        }
21    }
22
23    pub(crate) fn distance_wscale(&self, other: &tcp::Signature) -> Option<u32> {
24        if other.wscale.is_none() || self.wscale == other.wscale {
25            Some(tcp::TcpMatchQuality::High.as_score())
26        } else {
27            Some(tcp::TcpMatchQuality::Medium.as_score())
28        }
29    }
30
31    pub(crate) fn distance_olayout(&self, other: &tcp::Signature) -> Option<u32> {
32        if self.olayout == other.olayout {
33            Some(tcp::TcpMatchQuality::High.as_score())
34        } else {
35            None
36        }
37    }
38
39    pub(crate) fn distance_quirks(&self, other: &tcp::Signature) -> Option<u32> {
40        if self.quirks == other.quirks {
41            Some(tcp::TcpMatchQuality::High.as_score())
42        } else {
43            None
44        }
45    }
46}
47
48impl ObservedFingerprint for TcpObservation {
49    type Key = TcpIndexKey;
50
51    fn generate_index_key(&self) -> Self::Key {
52        let olayout_parts: Vec<String> = self.olayout.iter().map(|opt| format!("{opt}")).collect();
53        TcpIndexKey {
54            ip_version_key: self.version,
55            olayout_key: olayout_parts.join(","),
56            pclass_key: self.pclass,
57        }
58    }
59}
60
61impl DatabaseSignature<TcpObservation> for tcp::Signature {
62    fn calculate_distance(&self, observed: &TcpObservation) -> Option<u32> {
63        let distance = observed
64            .version
65            .distance_ip_version(&self.version)?
66            .saturating_add(observed.ittl.distance_ttl(&self.ittl)?)
67            .saturating_add(observed.distance_olen(self)?)
68            .saturating_add(observed.distance_mss(self)?)
69            .saturating_add(
70                observed
71                    .wsize
72                    .distance_window_size(&self.wsize, observed.mss)?,
73            )
74            .saturating_add(observed.distance_wscale(self)?)
75            .saturating_add(observed.distance_olayout(self)?)
76            .saturating_add(observed.distance_quirks(self)?)
77            .saturating_add(observed.pclass.distance_payload_size(&self.pclass)?);
78        Some(distance)
79    }
80
81    /// Returns the quality score based on the distance.
82    ///
83    /// The score is a value between 0.0 and 1.0, where 1.0 is a perfect match.
84    ///
85    /// The score is calculated based on the distance of the observed signal to the database signature.
86    /// The distance is a value between 0 and 18, where 0 is a perfect match and 18 is the maximum possible distance.
87    ///
88    fn get_quality_score(&self, distance: u32) -> f32 {
89        tcp::TcpMatchQuality::distance_to_score(distance)
90    }
91
92    fn generate_index_keys_for_db_entry(&self) -> Vec<TcpIndexKey> {
93        let mut keys = Vec::new();
94
95        let olayout_key_str = self
96            .olayout
97            .iter()
98            .map(|opt| format!("{opt}"))
99            .collect::<Vec<String>>()
100            .join(",");
101
102        let versions_for_keys = if self.version == IpVersion::Any {
103            vec![IpVersion::V4, IpVersion::V6]
104        } else {
105            vec![self.version]
106        };
107
108        let pclasses_for_keys = if self.pclass == PayloadSize::Any {
109            vec![PayloadSize::Zero, PayloadSize::NonZero]
110        } else {
111            vec![self.pclass]
112        };
113
114        for v_key_part in &versions_for_keys {
115            for pc_key_part in &pclasses_for_keys {
116                keys.push(TcpIndexKey {
117                    ip_version_key: *v_key_part,
118                    olayout_key: olayout_key_str.clone(),
119                    pclass_key: *pc_key_part,
120                });
121            }
122        }
123
124        keys
125    }
126}