shell_tunnel/session/
id.rs1use std::fmt;
4use std::str::FromStr;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7static COUNTER: AtomicU64 = AtomicU64::new(1);
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct SessionId(u64);
17
18impl SessionId {
19 pub fn new() -> Self {
21 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
22 }
23
24 pub fn as_u64(&self) -> u64 {
26 self.0
27 }
28
29 pub fn from_raw(value: u64) -> Self {
33 Self(value)
34 }
35}
36
37impl Default for SessionId {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl fmt::Display for SessionId {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "sess-{:08x}", self.0)
46 }
47}
48
49impl FromStr for SessionId {
50 type Err = crate::error::ShellTunnelError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 s.strip_prefix("sess-")
54 .and_then(|hex| u64::from_str_radix(hex, 16).ok())
55 .map(SessionId)
56 .ok_or_else(|| crate::error::ShellTunnelError::SessionNotFound(s.into()))
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use std::collections::HashSet;
64
65 #[test]
66 fn test_uniqueness() {
67 let mut ids = HashSet::new();
68 for _ in 0..10_000 {
69 let id = SessionId::new();
70 assert!(ids.insert(id), "Duplicate ID generated: {}", id);
71 }
72 assert_eq!(ids.len(), 10_000);
73 }
74
75 #[test]
76 fn test_display_format() {
77 let id = SessionId::from_raw(255);
78 assert_eq!(id.to_string(), "sess-000000ff");
79
80 let id2 = SessionId::from_raw(0x12345678);
81 assert_eq!(id2.to_string(), "sess-12345678");
82 }
83
84 #[test]
85 fn test_parse_valid() {
86 let id: SessionId = "sess-000000ff".parse().unwrap();
87 assert_eq!(id.as_u64(), 255);
88
89 let id2: SessionId = "sess-12345678".parse().unwrap();
90 assert_eq!(id2.as_u64(), 0x12345678);
91 }
92
93 #[test]
94 fn test_parse_invalid() {
95 assert!("000000ff".parse::<SessionId>().is_err());
97
98 assert!("session-000000ff".parse::<SessionId>().is_err());
100
101 assert!("sess-gggggggg".parse::<SessionId>().is_err());
103
104 assert!("".parse::<SessionId>().is_err());
106 }
107
108 #[test]
109 fn test_roundtrip() {
110 let original = SessionId::new();
111 let s = original.to_string();
112 let parsed: SessionId = s.parse().unwrap();
113 assert_eq!(original, parsed);
114 }
115
116 #[test]
117 fn test_hash_eq() {
118 let id1 = SessionId::from_raw(42);
119 let id2 = SessionId::from_raw(42);
120 let id3 = SessionId::from_raw(43);
121
122 assert_eq!(id1, id2);
123 assert_ne!(id1, id3);
124
125 let mut set = HashSet::new();
126 set.insert(id1);
127 assert!(set.contains(&id2));
128 assert!(!set.contains(&id3));
129 }
130}