Skip to main content

stackforge_core/anonymize/
port.rs

1//! Port generalization for transport layer anonymization.
2//!
3//! Ports are categorized into three IANA ranges and optionally replaced
4//! with sentinel values. Well-known destination ports (0-1023) can be
5//! preserved for service identification in ML models.
6
7/// IANA port category.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum PortCategory {
10    /// System / well-known ports (0-1023).
11    WellKnown,
12    /// Registered / user ports (1024-49151).
13    Registered,
14    /// Dynamic / ephemeral ports (49152-65535).
15    Ephemeral,
16}
17
18/// Sentinel values representing each port category.
19impl PortCategory {
20    /// Representative sentinel value for this category.
21    #[must_use]
22    pub const fn sentinel(self) -> u16 {
23        match self {
24            Self::WellKnown => 0,
25            Self::Registered => 1024,
26            Self::Ephemeral => 49152,
27        }
28    }
29}
30
31/// Classify a port into its IANA category.
32#[must_use]
33pub const fn categorize_port(port: u16) -> PortCategory {
34    match port {
35        0..=1023 => PortCategory::WellKnown,
36        1024..=49151 => PortCategory::Registered,
37        _ => PortCategory::Ephemeral,
38    }
39}
40
41/// Generalize a port to its category sentinel.
42///
43/// If `preserve_well_known_dst` is `true` and this is a destination port
44/// in the well-known range, the original port value is returned unchanged.
45#[must_use]
46pub const fn generalize_port(port: u16, preserve_well_known_dst: bool, is_dst: bool) -> u16 {
47    if preserve_well_known_dst && is_dst && port <= 1023 {
48        return port;
49    }
50    categorize_port(port).sentinel()
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_categorize() {
59        assert_eq!(categorize_port(80), PortCategory::WellKnown);
60        assert_eq!(categorize_port(443), PortCategory::WellKnown);
61        assert_eq!(categorize_port(8080), PortCategory::Registered);
62        assert_eq!(categorize_port(49152), PortCategory::Ephemeral);
63        assert_eq!(categorize_port(60000), PortCategory::Ephemeral);
64        assert_eq!(categorize_port(0), PortCategory::WellKnown);
65        assert_eq!(categorize_port(1023), PortCategory::WellKnown);
66        assert_eq!(categorize_port(1024), PortCategory::Registered);
67    }
68
69    #[test]
70    fn test_generalize_preserves_well_known_dst() {
71        // Destination port 443 preserved
72        assert_eq!(generalize_port(443, true, true), 443);
73        // Source port 443 NOT preserved
74        assert_eq!(generalize_port(443, true, false), 0);
75    }
76
77    #[test]
78    fn test_generalize_categorize_all() {
79        // Even dst ports are generalized when preserve_well_known_dst = false
80        assert_eq!(generalize_port(443, false, true), 0);
81        assert_eq!(generalize_port(8080, false, true), 1024);
82        assert_eq!(generalize_port(55000, false, true), 49152);
83    }
84
85    #[test]
86    fn test_generalize_ephemeral_src() {
87        assert_eq!(generalize_port(54321, true, false), 49152);
88    }
89}