Skip to main content

huginn_net_http/output/
common.rs

1use std::fmt;
2use std::fmt::Formatter;
3
4#[derive(Debug, Clone, PartialEq)]
5#[cfg_attr(feature = "json", derive(serde::Serialize))]
6pub struct IpPort {
7    pub ip: std::net::IpAddr,
8    pub port: u16,
9}
10
11impl IpPort {
12    pub fn new(ip: std::net::IpAddr, port: u16) -> Self {
13        Self { ip, port }
14    }
15}
16
17/// Whether a matched browser/web server label was a *specified* (concrete)
18/// or *generic* (catch-all) entry in the underlying database.
19///
20/// Defined locally so `huginn-net-http` does not depend on `huginn-net-db`.
21#[derive(Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "json", derive(serde::Serialize))]
23pub enum OsKind {
24    Specified,
25    Generic,
26}
27
28impl fmt::Display for OsKind {
29    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30        f.write_str(match self {
31            OsKind::Specified => "Specified",
32            OsKind::Generic => "Generic",
33        })
34    }
35}
36
37/// Which tier an HTTP match landed in: `Specific` beats `Generic`.
38///
39/// HTTP signatures have no tolerances: a field that disagrees rejects the
40/// signature, so there is no fuzzy tier here, unlike TCP.
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub enum MatchRank {
43    /// Fit against a signature naming a concrete product.
44    Specific,
45    /// Fit against a catch-all signature.
46    Generic,
47}
48
49impl MatchRank {
50    /// Score in `[0.0, 1.0]` for this tier. The ordering is the contract; the
51    /// values are free to be recalibrated.
52    pub fn as_quality(&self) -> f32 {
53        match self {
54            MatchRank::Specific => 1.0,
55            MatchRank::Generic => 0.8,
56        }
57    }
58}
59
60/// Quality classification for an HTTP match.
61///
62/// - `Matched(rank)` a signature was matched, in the carried tier.
63/// - `NotMatched` the matcher was active but no signature was a viable fit.
64/// - `Disabled` matching was disabled (no matcher plugged in).
65#[derive(Clone, Debug)]
66pub enum MatchQuality {
67    Matched(MatchRank),
68    NotMatched,
69    Disabled,
70}
71
72/// The wire format keeps the tier numeric: `Matched` serializes as its score.
73#[cfg(feature = "json")]
74impl serde::Serialize for MatchQuality {
75    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
76        match self {
77            MatchQuality::Matched(rank) => serializer.serialize_newtype_variant(
78                "MatchQuality",
79                0,
80                "Matched",
81                &rank.as_quality(),
82            ),
83            MatchQuality::NotMatched => {
84                serializer.serialize_unit_variant("MatchQuality", 1, "NotMatched")
85            }
86            MatchQuality::Disabled => {
87                serializer.serialize_unit_variant("MatchQuality", 2, "Disabled")
88            }
89        }
90    }
91}
92
93/// Represents a browser identified from an HTTP request signature.
94#[derive(Debug, Clone)]
95#[cfg_attr(feature = "json", derive(serde::Serialize))]
96pub struct Browser {
97    pub name: String,
98    pub family: Option<String>,
99    pub variant: Option<String>,
100    pub kind: OsKind,
101}
102
103/// Represents a web server identified from an HTTP response signature.
104#[derive(Debug, Clone)]
105#[cfg_attr(feature = "json", derive(serde::Serialize))]
106pub struct WebServer {
107    pub name: String,
108    pub family: Option<String>,
109    pub variant: Option<String>,
110    pub kind: OsKind,
111}