Skip to main content

unifly_api/convert/
client.rs

1use chrono::Utc;
2
3use crate::integration_types;
4use crate::model::client::{Client, ClientType, GuestAuth, WirelessInfo};
5use crate::model::common::DataSource;
6use crate::model::entity_id::{EntityId, MacAddress};
7use crate::session::models::SessionClientEntry;
8
9use super::helpers::{parse_ip, parse_iso};
10
11// ── Session API ──────────────────────────────────────────────────
12
13fn channel_to_frequency(channel: Option<i32>) -> Option<f32> {
14    channel.map(|ch| match ch {
15        1..=14 => 2.4,
16        32..=68 | 96..=177 => 5.0,
17        _ => 6.0,
18    })
19}
20
21impl From<SessionClientEntry> for Client {
22    fn from(c: SessionClientEntry) -> Self {
23        let is_wired = c.is_wired.unwrap_or(false);
24        let client_type = if is_wired {
25            ClientType::Wired
26        } else {
27            ClientType::Wireless
28        };
29
30        let wireless = if is_wired {
31            None
32        } else {
33            Some(WirelessInfo {
34                ssid: c.essid.clone(),
35                bssid: c.bssid.as_deref().map(MacAddress::new),
36                channel: c.channel.and_then(|ch| ch.try_into().ok()),
37                frequency_ghz: channel_to_frequency(c.channel),
38                signal_dbm: c.signal.or(c.rssi),
39                noise_dbm: c.noise,
40                satisfaction: c.satisfaction.and_then(|s| s.try_into().ok()),
41                tx_rate_kbps: c.tx_rate.and_then(|r| r.try_into().ok()),
42                rx_rate_kbps: c.rx_rate.and_then(|r| r.try_into().ok()),
43            })
44        };
45
46        let is_guest = c.is_guest.unwrap_or(false);
47        let guest_auth = if is_guest {
48            Some(GuestAuth {
49                authorized: c.authorized.unwrap_or(false),
50                method: None,
51                expires_at: None,
52                tx_bytes: c.tx_bytes.and_then(|b| b.try_into().ok()),
53                rx_bytes: c.rx_bytes.and_then(|b| b.try_into().ok()),
54                elapsed_minutes: None,
55            })
56        } else {
57            None
58        };
59
60        let uplink_device_mac = if is_wired {
61            c.sw_mac.as_deref().map(MacAddress::new)
62        } else {
63            c.ap_mac.as_deref().map(MacAddress::new)
64        };
65
66        let connected_at = c.uptime.and_then(|secs| {
67            let duration = chrono::Duration::seconds(secs);
68            Utc::now().checked_sub_signed(duration)
69        });
70
71        Client {
72            id: EntityId::from(c.id),
73            mac: MacAddress::new(&c.mac),
74            ip: parse_ip(c.ip.as_ref()),
75            name: c.name,
76            hostname: c.hostname,
77            client_type,
78            connected_at,
79            uplink_device_id: None,
80            uplink_device_mac,
81            network_id: c.network_id.map(EntityId::from),
82            vlan: None,
83            wireless,
84            guest_auth,
85            is_guest,
86            tx_bytes: c.tx_bytes.and_then(|b| b.try_into().ok()),
87            rx_bytes: c.rx_bytes.and_then(|b| b.try_into().ok()),
88            bandwidth: None,
89            os_name: None,
90            device_class: None,
91            use_fixedip: false,
92            fixed_ip: None,
93            blocked: c.blocked.unwrap_or(false),
94            source: DataSource::SessionApi,
95            updated_at: Utc::now(),
96        }
97    }
98}
99
100// ── Integration API ──────────────────────────────────────────────
101
102impl From<integration_types::ClientResponse> for Client {
103    fn from(c: integration_types::ClientResponse) -> Self {
104        let client_type = match c.client_type.as_str() {
105            "WIRED" => ClientType::Wired,
106            "WIRELESS" => ClientType::Wireless,
107            "VPN" => ClientType::Vpn,
108            "TELEPORT" => ClientType::Teleport,
109            _ => ClientType::Unknown,
110        };
111
112        let uuid_fallback = c.id.to_string();
113        let mac_str = c
114            .mac_address
115            .as_deref()
116            .filter(|s| !s.is_empty())
117            .unwrap_or(&uuid_fallback);
118
119        Client {
120            id: EntityId::Uuid(c.id),
121            mac: MacAddress::new(mac_str),
122            ip: c.ip_address.as_deref().and_then(|s| s.parse().ok()),
123            name: Some(c.name),
124            hostname: None,
125            client_type,
126            connected_at: c.connected_at.as_deref().and_then(parse_iso),
127            uplink_device_id: None,
128            uplink_device_mac: None,
129            network_id: None,
130            vlan: None,
131            wireless: None,
132            guest_auth: None,
133            is_guest: false,
134            tx_bytes: None,
135            rx_bytes: None,
136            bandwidth: None,
137            os_name: None,
138            device_class: None,
139            use_fixedip: false,
140            fixed_ip: None,
141            blocked: false,
142            source: DataSource::IntegrationApi,
143            updated_at: Utc::now(),
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn channel_frequency_bands() {
154        assert_eq!(channel_to_frequency(Some(6)), Some(2.4));
155        assert_eq!(channel_to_frequency(Some(36)), Some(5.0));
156        assert_eq!(channel_to_frequency(Some(149)), Some(5.0));
157        assert_eq!(channel_to_frequency(None), None);
158    }
159}