1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4const SERVICE_PORTS: &[(&str, u16)] = &[
5 ("ftp", 21),
6 ("ssh", 22),
7 ("smtp", 25),
8 ("dns", 53),
9 ("http", 80),
10 ("pop3", 110),
11 ("ntp", 123),
12 ("imap", 143),
13 ("ldap", 389),
14 ("https", 443),
15 ("mysql", 3306),
16 ("postgres", 5432),
17 ("redis", 6379),
18];
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Port {
23 pub value: u16,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum PortRange {
30 System,
32 Registered,
34 Dynamic,
36}
37
38pub fn is_valid_port(input: u32) -> bool {
40 input <= u16::MAX as u32
41}
42
43pub fn parse_port(input: &str) -> Option<Port> {
45 let trimmed = input.trim();
46
47 if trimmed.is_empty() {
48 return None;
49 }
50
51 trimmed.parse::<u16>().ok().map(|value| Port { value })
52}
53
54pub fn port_range(port: u16) -> PortRange {
56 match port {
57 0..=1023 => PortRange::System,
58 1024..=49151 => PortRange::Registered,
59 _ => PortRange::Dynamic,
60 }
61}
62
63pub fn is_system_port(port: u16) -> bool {
65 matches!(port_range(port), PortRange::System)
66}
67
68pub fn is_registered_port(port: u16) -> bool {
70 matches!(port_range(port), PortRange::Registered)
71}
72
73pub fn is_dynamic_port(port: u16) -> bool {
75 matches!(port_range(port), PortRange::Dynamic)
76}
77
78pub fn common_port_name(port: u16) -> Option<&'static str> {
80 SERVICE_PORTS
81 .iter()
82 .find(|(_, candidate_port)| *candidate_port == port)
83 .map(|(service, _)| *service)
84}
85
86pub fn default_port_for_service(service: &str) -> Option<u16> {
88 let normalized = service.trim().to_ascii_lowercase();
89
90 SERVICE_PORTS
91 .iter()
92 .find(|(candidate_service, _)| *candidate_service == normalized)
93 .map(|(_, port)| *port)
94}