1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use std::{
    fmt::Display,
    net::SocketAddr,
    time::{Duration, Instant},
};

/// Connection identifier
/// 
/// #Variants
/// * Initiator: Indicates a Clt connection or the side of the link which initiated the connection
/// * Acceptor: Indicates a Svc connection or the side of the link which accepted the connection
#[derive(Debug, Clone, PartialEq)]
pub enum ConId {
    Initiator { name: String, local: Option<SocketAddr>, peer: SocketAddr },
    Acceptor { name: String, local: SocketAddr, peer: Option<SocketAddr> },
}
impl ConId {
    pub fn clt(name: Option<&str>, local: Option<&str>, peer: &str) -> Self {
        ConId::Initiator {
            name: name.unwrap_or("unknown").to_owned(),
            local: local.map(|addr| addr.parse().unwrap_or_else(|_| panic!("unable to parse addr: {:?}", addr))),
            peer: peer.parse().unwrap_or_else(|_| panic!("unable to parse addr: {:?}", peer)),
        }
    }
    pub fn set_local(&mut self, local: SocketAddr) {
        match self {
            ConId::Initiator { local: l, .. } => *l = Some(local),
            ConId::Acceptor { local: l, .. } => *l = local,
        }
    }
    pub fn set_peer(&mut self, peer: SocketAddr) {
        match self {
            ConId::Initiator { peer: p, .. } => *p = peer,
            ConId::Acceptor { peer: p, .. } => *p = Some(peer),
        }
    }

    pub fn svc(name: Option<&str>, local: &str, peer: Option<&str>) -> Self {
        ConId::Acceptor {
            name: name.unwrap_or("unknown").to_owned(),
            local: local.parse().unwrap_or_else(|_| panic!("unable to parse addr: {:?}", local)),
            peer: peer.map(|addr| addr.parse().unwrap_or_else(|_| panic!("unable to parse addr: {:?}", addr))),
        }
    }
    pub fn name(&self) -> &str {
        match self {
            ConId::Initiator { name, .. } => name,
            ConId::Acceptor { name, .. } => name,
        }
    }
    pub fn get_peer(&self) -> Option<SocketAddr> {
        match self {
            ConId::Initiator { peer, .. } => Some(*peer),
            ConId::Acceptor { peer, .. } => *peer,
        }
    }
    pub fn get_local(&self) -> Option<SocketAddr> {
        match self {
            ConId::Initiator { local, .. } => *local,
            ConId::Acceptor { local, .. } => Some(*local),
        }
    }
    pub fn from_same_lineage(&self, other: &Self) -> bool {
        match (self, other) {
            // listening ports are unique hence must be ( self IS other | other is a Clt that was started by self )
            (ConId::Acceptor { local: l1, .. }, ConId::Acceptor { local: l2, .. }) => l1 == l2, 
            _ => false,
        }
    }
}
impl Default for ConId {
    fn default() -> Self {
        ConId::clt(None, None, "0.0.0.0:0")
    }
}
impl Display for ConId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConId::Initiator { name, local, peer } => {
                write!(
                    f,
                    "Initiator({name}@{}->{peer})",
                    match local {
                        Some(local) => format!("{}", local),
                        None => "pending".to_owned(),
                    }
                )
            }
            ConId::Acceptor { name, local, peer } => {
                write!(
                    f,
                    "Acceptor({name}@{local}<-{})",
                    match peer {
                        Some(peer) => format!("{}", peer),
                        None => "pending".to_owned(),
                    }
                )
            }
        }
    }
}

/// Trait that maintain an active connection can disclose its connection information via [ConId] struct
pub trait ConnectionId {
    fn con_id(&self) -> &ConId;
}

pub trait ConnectionStatus {
    /// logical check of connection status
    fn is_connected(&self) -> bool;
    fn is_connected_busywait_timeout(&self, timeout: Duration) -> bool {
        let start = Instant::now();
        while start.elapsed() < timeout {
            if self.is_connected() {
                return true;
            }
            std::hint::spin_loop();
        }
        // can't assume false at this point and need to recheck in case timeout arg is Duration::ZERO
        self.is_connected() 
    }
}
/// Provides methods for testing status of connections in the Pool
pub trait PoolConnectionStatus {
    fn is_next_connected(&mut self) -> bool;
    fn is_next_connected_busywait_timeout(&mut self, timeout: Duration) -> bool {
        let start = Instant::now();
        while start.elapsed() < timeout {
            if self.is_next_connected() {
                return true;
            }
            std::hint::spin_loop();
        }
        // can't assume false at this point and need to recheck in case timeout arg is Duration::ZERO
        self.is_next_connected()
    }
    fn all_connected(&mut self) -> bool;
    fn all_connected_busywait_timeout(&mut self, timeout: Duration) -> bool {
        let start = Instant::now();
        while start.elapsed() < timeout {
            if self.all_connected() {
                return true;
            }
            std::hint::spin_loop();
        }
        // can't assume false at this point and need to recheck in case timeout arg is Duration::ZERO
        self.all_connected()
    }
}

#[cfg(test)]
mod test {

    use log::info;

    use crate::prelude::*;
    use crate::unittest::setup;

    #[test]
    fn test_con_id() {
        setup::log::configure();
        let con_id = ConId::clt(Some("unittest"), None, "0.0.0.0:1");
        info!("con_id: {:?}", con_id);
        info!("con_id: {}", con_id);
        assert_eq!(con_id.to_string(), "Initiator(unittest@pending->0.0.0.0:1)");

        let con_id = ConId::svc(Some("unittest"), "0.0.0.0:1", None);
        info!("con_id: {:?}", con_id);
        info!("con_id: {}", con_id);
        assert_eq!(con_id.to_string(), "Acceptor(unittest@0.0.0.0:1<-pending)");
    }
}