1use std::collections::HashMap;
2use std::net::{IpAddr, Ipv4Addr};
3
4use serde_json::Value;
5
6use crate::integration_types;
7use crate::model::common::DataSource;
8use crate::model::entity_id::EntityId;
9use crate::model::network::{DhcpConfig, Ipv6Mode, Network, NetworkManagement};
10
11use super::helpers::map_origin;
12
13fn net_field<'a>(
14 extra: &'a HashMap<String, Value>,
15 metadata: &'a Value,
16 key: &str,
17) -> Option<&'a Value> {
18 extra.get(key).or_else(|| metadata.get(key))
19}
20
21#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
22fn parse_network_fields(
23 id: uuid::Uuid,
24 name: String,
25 enabled: bool,
26 management_str: &str,
27 vlan_id: i32,
28 is_default: bool,
29 metadata: &Value,
30 extra: &HashMap<String, Value>,
31) -> Network {
32 let isolation_enabled = net_field(extra, metadata, "isolationEnabled")
34 .and_then(Value::as_bool)
35 .unwrap_or(false);
36 let internet_access_enabled = net_field(extra, metadata, "internetAccessEnabled")
37 .and_then(Value::as_bool)
38 .unwrap_or(true);
39 let mdns_forwarding_enabled = net_field(extra, metadata, "mdnsForwardingEnabled")
40 .and_then(Value::as_bool)
41 .unwrap_or(false);
42 let cellular_backup_enabled = net_field(extra, metadata, "cellularBackupEnabled")
43 .and_then(Value::as_bool)
44 .unwrap_or(false);
45
46 let firewall_zone_id = net_field(extra, metadata, "zoneId")
48 .and_then(Value::as_str)
49 .and_then(|s| uuid::Uuid::parse_str(s).ok())
50 .map(EntityId::Uuid);
51
52 let ipv4 = net_field(extra, metadata, "ipv4Configuration");
54
55 let gateway_ip: Option<Ipv4Addr> = ipv4
56 .and_then(|v| v.get("hostIpAddress").or_else(|| v.get("host")))
57 .and_then(Value::as_str)
58 .and_then(|s| s.parse().ok());
59
60 let subnet = ipv4.and_then(|v| {
61 let host = v.get("hostIpAddress").or_else(|| v.get("host"))?.as_str()?;
62 let prefix = v
63 .get("prefixLength")
64 .or_else(|| v.get("prefix"))?
65 .as_u64()?;
66 Some(format!("{host}/{prefix}"))
67 });
68
69 let dhcp = ipv4.and_then(|v| {
71 if let Some(dhcp_cfg) = v.get("dhcpConfiguration") {
72 let mode = dhcp_cfg.get("mode").and_then(Value::as_str).unwrap_or("");
73 let dhcp_enabled = mode == "SERVER";
74 let range = dhcp_cfg.get("ipAddressRange");
75 let range_start = range
76 .and_then(|r| r.get("start").or_else(|| r.get("rangeStart")))
77 .and_then(Value::as_str)
78 .and_then(|s| s.parse().ok());
79 let range_stop = range
80 .and_then(|r| r.get("end").or_else(|| r.get("rangeStop")))
81 .and_then(Value::as_str)
82 .and_then(|s| s.parse().ok());
83 let lease_time_secs = dhcp_cfg.get("leaseTimeSeconds").and_then(Value::as_u64);
84 let dns_servers = dhcp_cfg
85 .get("dnsServerIpAddressesOverride")
86 .and_then(Value::as_array)
87 .map(|arr| {
88 arr.iter()
89 .filter_map(|v| v.as_str()?.parse::<IpAddr>().ok())
90 .collect()
91 })
92 .unwrap_or_default();
93 return Some(DhcpConfig {
94 enabled: dhcp_enabled,
95 range_start,
96 range_stop,
97 lease_time_secs,
98 dns_servers,
99 gateway: gateway_ip,
100 });
101 }
102
103 let server = v.get("dhcp")?.get("server")?;
104 let dhcp_enabled = server
105 .get("enabled")
106 .and_then(Value::as_bool)
107 .unwrap_or(false);
108 let range_start = server
109 .get("rangeStart")
110 .and_then(Value::as_str)
111 .and_then(|s| s.parse().ok());
112 let range_stop = server
113 .get("rangeStop")
114 .and_then(Value::as_str)
115 .and_then(|s| s.parse().ok());
116 let lease_time_secs = server.get("leaseTimeSec").and_then(Value::as_u64);
117 let dns_servers = server
118 .get("dnsOverride")
119 .and_then(|d| d.get("servers"))
120 .and_then(Value::as_array)
121 .map(|arr| {
122 arr.iter()
123 .filter_map(|v| v.as_str()?.parse::<IpAddr>().ok())
124 .collect()
125 })
126 .unwrap_or_default();
127 let gateway = server
128 .get("gateway")
129 .and_then(Value::as_str)
130 .and_then(|s| s.parse().ok())
131 .or(gateway_ip);
132 Some(DhcpConfig {
133 enabled: dhcp_enabled,
134 range_start,
135 range_stop,
136 lease_time_secs,
137 dns_servers,
138 gateway,
139 })
140 });
141
142 let pxe_enabled = ipv4
144 .and_then(|v| v.get("pxe"))
145 .and_then(|v| v.get("enabled"))
146 .and_then(Value::as_bool)
147 .unwrap_or(false);
148 let ntp_server = ipv4
149 .and_then(|v| v.get("ntp"))
150 .and_then(|v| v.get("server"))
151 .and_then(Value::as_str)
152 .and_then(|s| s.parse::<IpAddr>().ok());
153 let tftp_server = ipv4
154 .and_then(|v| v.get("tftp"))
155 .and_then(|v| v.get("server"))
156 .and_then(Value::as_str)
157 .map(String::from);
158
159 let ipv6 = net_field(extra, metadata, "ipv6Configuration");
161 let ipv6_enabled = ipv6.is_some();
162 let ipv6_mode = ipv6
163 .and_then(|v| v.get("interfaceType").or_else(|| v.get("type")))
164 .and_then(Value::as_str)
165 .and_then(|s| match s {
166 "PREFIX_DELEGATION" => Some(Ipv6Mode::PrefixDelegation),
167 "STATIC" => Some(Ipv6Mode::Static),
168 _ => None,
169 });
170 let slaac_enabled = ipv6
171 .and_then(|v| {
172 v.get("clientAddressAssignment")
173 .and_then(|ca| ca.get("slaacEnabled"))
174 .and_then(Value::as_bool)
175 .or_else(|| {
176 v.get("slaac")
177 .and_then(|s| s.get("enabled"))
178 .and_then(Value::as_bool)
179 })
180 })
181 .unwrap_or(false);
182 let dhcpv6_enabled = ipv6
183 .and_then(|v| {
184 v.get("clientAddressAssignment")
185 .and_then(|ca| ca.get("dhcpv6Enabled"))
186 .and_then(Value::as_bool)
187 .or_else(|| {
188 v.get("dhcpv6")
189 .and_then(|d| d.get("enabled"))
190 .and_then(Value::as_bool)
191 })
192 })
193 .unwrap_or(false);
194 let ipv6_prefix = ipv6.and_then(|v| {
195 v.get("additionalHostIpSubnets")
196 .and_then(Value::as_array)
197 .and_then(|a| a.first())
198 .and_then(Value::as_str)
199 .map(String::from)
200 .or_else(|| v.get("prefix").and_then(Value::as_str).map(String::from))
201 });
202
203 let has_ipv4_config = ipv4.is_some();
205 let has_device_id = extra.contains_key("deviceId");
206 let management = if has_ipv4_config && !has_device_id {
207 Some(NetworkManagement::Gateway)
208 } else if has_device_id {
209 Some(NetworkManagement::Switch)
210 } else if has_ipv4_config {
211 Some(NetworkManagement::Gateway)
212 } else {
213 None
214 };
215
216 Network {
217 id: EntityId::Uuid(id),
218 name,
219 enabled,
220 management,
221 purpose: None,
222 is_default,
223 #[allow(
224 clippy::as_conversions,
225 clippy::cast_possible_truncation,
226 clippy::cast_sign_loss
227 )]
228 vlan_id: Some(vlan_id as u16),
229 subnet,
230 gateway_ip,
231 dhcp,
232 ipv6_enabled,
233 ipv6_mode,
234 ipv6_prefix,
235 dhcpv6_enabled,
236 slaac_enabled,
237 ntp_server,
238 pxe_enabled,
239 tftp_server,
240 firewall_zone_id,
241 isolation_enabled,
242 internet_access_enabled,
243 mdns_forwarding_enabled,
244 cellular_backup_enabled,
245 origin: map_origin(management_str),
246 source: DataSource::IntegrationApi,
247 }
248}
249
250impl From<integration_types::NetworkResponse> for Network {
251 fn from(n: integration_types::NetworkResponse) -> Self {
252 parse_network_fields(
253 n.id,
254 n.name,
255 n.enabled,
256 &n.management,
257 n.vlan_id,
258 n.default,
259 &n.metadata,
260 &n.extra,
261 )
262 }
263}
264
265impl From<integration_types::NetworkDetailsResponse> for Network {
266 fn from(n: integration_types::NetworkDetailsResponse) -> Self {
267 parse_network_fields(
268 n.id,
269 n.name,
270 n.enabled,
271 &n.management,
272 n.vlan_id,
273 n.default,
274 &n.metadata,
275 &n.extra,
276 )
277 }
278}