1#![forbid(unsafe_code)]
4
5use super::error::DomainError;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt;
8use std::num::NonZeroU16;
9
10#[repr(transparent)]
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct SshPort(NonZeroU16);
14
15impl SshPort {
16 pub fn try_new(port: u16) -> Result<Self, DomainError> {
18 NonZeroU16::new(port)
19 .map(Self)
20 .ok_or_else(|| DomainError::new("ssh_port", "invalid SSH port: 0 (use 1..=65535)"))
21 }
22
23 #[must_use]
25 pub const fn get(self) -> u16 {
26 self.0.get()
27 }
28
29 #[must_use]
31 pub const fn as_u16(self) -> u16 {
32 self.get()
33 }
34}
35
36impl fmt::Display for SshPort {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(f, "{}", self.get())
39 }
40}
41
42impl Serialize for SshPort {
43 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
44 s.serialize_u16(self.get())
45 }
46}
47
48impl<'de> Deserialize<'de> for SshPort {
49 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
50 let v = u16::deserialize(d)?;
51 Self::try_new(v).map_err(serde::de::Error::custom)
52 }
53}
54
55#[repr(transparent)]
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
60pub struct BindPort(u16);
61
62impl BindPort {
63 #[must_use]
65 pub const fn new(port: u16) -> Self {
66 Self(port)
67 }
68
69 #[must_use]
71 pub const fn get(self) -> u16 {
72 self.0
73 }
74
75 #[must_use]
77 pub const fn is_ephemeral(self) -> bool {
78 self.0 == 0
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use std::mem::{align_of, size_of};
86
87 #[test]
88 fn ssh_port_rejects_zero() {
89 assert!(SshPort::try_new(0).is_err());
90 assert_eq!(SshPort::try_new(22).unwrap().get(), 22);
91 }
92
93 #[test]
94 fn ssh_port_zero_cost() {
95 assert_eq!(size_of::<SshPort>(), size_of::<u16>());
96 assert_eq!(size_of::<Option<SshPort>>(), size_of::<u16>());
97 assert_eq!(align_of::<SshPort>(), align_of::<u16>());
98 }
99
100 #[test]
101 fn bind_port_allows_zero() {
102 assert!(BindPort::new(0).is_ephemeral());
103 assert!(!BindPort::new(8080).is_ephemeral());
104 }
105}