1use {
6 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
7 fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy,
8 fidl_fuchsia_posix_socket as fposix_socket,
9};
10
11use socket_proxy::{NetworkConversionError, NetworkExt, NetworkRegistryError};
12use std::collections::hash_map::Entry;
13use std::collections::HashMap;
14use thiserror::Error;
15use todo_unused::todo_unused;
16
17use crate::InterfaceId;
18
19#[derive(Debug)]
20pub struct SocketProxyState {
21 fuchsia_networks: fnp_socketproxy::FuchsiaNetworksProxy,
22 default_id: Option<InterfaceId>,
23 networks: HashMap<InterfaceId, fnp_socketproxy::Network>,
24}
25
26impl SocketProxyState {
30 #[todo_unused("https://fxbug.dev/385368910")]
31 pub fn new(fuchsia_networks: fnp_socketproxy::FuchsiaNetworksProxy) -> Self {
32 Self { fuchsia_networks, default_id: None, networks: HashMap::new() }
33 }
34
35 #[todo_unused("https://fxbug.dev/385368910")]
36 pub(crate) async fn handle_default_network(
41 &mut self,
42 network_id: Option<InterfaceId>,
43 ) -> Result<(), SocketProxyError> {
44 if self.default_id == network_id {
46 return Ok(());
47 }
48
49 let socketproxy_network_id = match network_id {
50 Some(id) => {
51 if !self.networks.contains_key(&id) {
52 return Err(SocketProxyError::SetDefaultNonexistentNetwork(id));
53 }
54
55 let id_u32: u32 = match id.get().try_into() {
57 Err(_) => {
58 return Err(SocketProxyError::InvalidInterfaceId(id));
59 }
60 Ok(id) => id,
61 };
62
63 fposix_socket::OptionalUint32::Value(id_u32)
64 }
65 None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
66 };
67
68 self.default_id = network_id;
69
70 Ok(self.fuchsia_networks.set_default(&socketproxy_network_id).await??)
71 }
72
73 #[todo_unused("https://fxbug.dev/385368910")]
74 pub(crate) async fn handle_default_network_removal(
78 &mut self,
79 ) -> Result<Option<InterfaceId>, SocketProxyError> {
80 if let None = self.default_id {
81 return Ok(None);
83 }
84 let mut interface_ids = self
85 .networks
86 .keys()
87 .filter(|network| Some(network.get()) != self.default_id.map(|id| id.get()))
88 .cloned()
89 .peekable();
90 if interface_ids.peek().is_none() {
91 self.handle_default_network(None).await.map(|_| None)
94 } else {
95 let new_id = interface_ids.into_iter().min();
97 self.handle_default_network(new_id).await.map(|_| new_id)
98 }
99 }
100
101 #[todo_unused("https://fxbug.dev/385368910")]
102 pub(crate) async fn handle_add_network(
107 &mut self,
108 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
109 ) -> Result<(), SocketProxyError> {
110 let network = fnp_socketproxy::Network::from_watcher_properties(properties)?;
112 match self.networks.entry(InterfaceId(properties.id)) {
113 Entry::Vacant(entry) => {
114 let _ = entry.insert(network.clone());
115 }
116 Entry::Occupied(_entry) => {
117 return Err(SocketProxyError::AddedExistingNetwork(network));
118 }
119 }
120
121 Ok(self.fuchsia_networks.add(&network).await??)
122 }
123
124 #[todo_unused("https://fxbug.dev/385368910")]
125 pub(crate) async fn handle_remove_network(
130 &mut self,
131 network_id: InterfaceId,
132 ) -> Result<(), SocketProxyError> {
133 if !self.networks.contains_key(&network_id) {
134 return Err(SocketProxyError::RemovedNonexistentNetwork(network_id));
135 } else if self.default_id.map(|id| id == network_id).unwrap_or(false) {
136 return Err(SocketProxyError::RemovedDefaultNetwork(network_id));
137 }
138
139 let id_u32: u32 = match network_id.get().try_into() {
141 Err(_) => {
142 return Err(SocketProxyError::InvalidInterfaceId(network_id));
143 }
144 Ok(id) => id,
145 };
146 let _ = self.networks.remove(&network_id);
147
148 Ok(self.fuchsia_networks.remove(id_u32).await??)
149 }
150}
151
152#[todo_unused("https://fxbug.dev/385368910")]
153#[derive(Clone, Debug, Error)]
156pub enum SocketProxyError {
157 #[error("Error adding network that already exists: {0:?}")]
158 AddedExistingNetwork(fnp_socketproxy::Network),
159 #[error("Error converting the watcher properties to a network: {0}")]
160 ConversionError(#[from] NetworkConversionError),
161 #[error("Error calling FIDL on socketproxy: {0:?}")]
162 Fidl(#[from] fidl::Error),
163 #[error("Error converting id to socketproxy network: {0}")]
164 InvalidInterfaceId(InterfaceId),
165 #[error("Network Registry error: {0:?}")]
166 NetworkRegistry(#[from] NetworkRegistryError),
167 #[error("Error removing a current default network with id: {0}")]
168 RemovedDefaultNetwork(InterfaceId),
169 #[error("Error removing network that does not exist with id: {0}")]
170 RemovedNonexistentNetwork(InterfaceId),
171 #[error("Error setting default network that does not exist with id: {0}")]
172 SetDefaultNonexistentNetwork(InterfaceId),
173}
174
175impl From<fnp_socketproxy::NetworkRegistryAddError> for SocketProxyError {
176 fn from(error: fnp_socketproxy::NetworkRegistryAddError) -> Self {
177 SocketProxyError::NetworkRegistry(NetworkRegistryError::Add(error))
178 }
179}
180
181impl From<fnp_socketproxy::NetworkRegistryRemoveError> for SocketProxyError {
182 fn from(error: fnp_socketproxy::NetworkRegistryRemoveError) -> Self {
183 SocketProxyError::NetworkRegistry(NetworkRegistryError::Remove(error))
184 }
185}
186
187impl From<fnp_socketproxy::NetworkRegistrySetDefaultError> for SocketProxyError {
188 fn from(error: fnp_socketproxy::NetworkRegistrySetDefaultError) -> Self {
189 SocketProxyError::NetworkRegistry(NetworkRegistryError::SetDefault(error))
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use socket_proxy_testing::respond_to_socketproxy;
196 use std::num::NonZeroU64;
197
198 use super::*;
199
200 fn interface_properties_from_id(
201 id: u64,
202 ) -> fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest> {
203 fnet_interfaces_ext::Properties {
204 id: NonZeroU64::new(id).expect("this is a valid u64"),
205 online: true,
206 name: String::from("network"),
207 has_default_ipv4_route: true,
208 has_default_ipv6_route: true,
209 addresses: vec![],
210 port_class: fnet_interfaces_ext::PortClass::Ethernet,
211 }
212 }
213
214 #[fuchsia::test]
215 async fn test_set_default_network_id() -> Result<(), SocketProxyError> {
216 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
217 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
218 let mut state = SocketProxyState::new(fuchsia_networks);
219 const NETWORK_ID_U64: u64 = 1u64;
220 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
221
222 assert_matches::assert_matches!(
225 state.handle_default_network(Some(NETWORK_ID)).await,
226 Err(SocketProxyError::SetDefaultNonexistentNetwork(id))
227 if id.get() == NETWORK_ID_U64
228 );
229
230 assert_matches::assert_matches!(
233 state.networks.insert(
234 NETWORK_ID,
235 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
236 NETWORK_ID_U64
237 ))?,
238 ),
239 None
240 );
241
242 let (set_default_network_result, ()) = futures::join!(
244 state.handle_default_network(Some(NETWORK_ID)),
245 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
246 );
247 assert_matches::assert_matches!(set_default_network_result, Ok(()));
248
249 let (set_default_network_result2, ()) = futures::join!(
251 state.handle_default_network(None),
252 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
253 );
254 assert_matches::assert_matches!(set_default_network_result2, Ok(()));
255
256 Ok(())
257 }
258
259 #[fuchsia::test]
260 async fn test_add_network() {
261 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
262 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
263 let mut state = SocketProxyState::new(fuchsia_networks);
264 const NETWORK_ID1_U32: u32 = 1u32;
265 const NETWORK_ID2_U64: u64 = 2u64;
266
267 let network1 = interface_properties_from_id(NETWORK_ID1_U32.into());
268 let (add_network_result, ()) = futures::join!(
269 state.handle_add_network(&network1),
270 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
271 );
272 assert_matches::assert_matches!(add_network_result, Ok(()));
273
274 assert_matches::assert_matches!(
276 state.handle_add_network(&network1).await,
277 Err(SocketProxyError::AddedExistingNetwork(fnp_socketproxy::Network {
278 network_id: Some(NETWORK_ID1_U32),
279 ..
280 }))
281 );
282
283 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
285 let (add_network_result2, ()) = futures::join!(
286 state.handle_add_network(&network2),
287 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
288 );
289 assert_matches::assert_matches!(add_network_result2, Ok(()));
290 }
291
292 #[fuchsia::test]
293 async fn test_remove_network() -> Result<(), SocketProxyError> {
294 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
295 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
296 let mut state = SocketProxyState::new(fuchsia_networks);
297 const NETWORK_ID_U64: u64 = 1;
298 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
299
300 assert_matches::assert_matches!(
303 state.handle_remove_network(NETWORK_ID).await,
304 Err(SocketProxyError::RemovedNonexistentNetwork(id))
305 if id.get() == NETWORK_ID_U64
306 );
307
308 assert_matches::assert_matches!(
311 state.networks.insert(
312 NETWORK_ID,
313 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
314 NETWORK_ID_U64
315 ),)?,
316 ),
317 None
318 );
319 state.default_id = Some(NETWORK_ID);
320
321 assert_matches::assert_matches!(
323 state.handle_remove_network(NETWORK_ID).await,
324 Err(SocketProxyError::RemovedDefaultNetwork(id))
325 if id.get() == NETWORK_ID_U64
326 );
327
328 state.default_id = None;
331
332 let (remove_network_result, ()) = futures::join!(
335 state.handle_remove_network(NETWORK_ID),
336 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
337 );
338 assert_matches::assert_matches!(remove_network_result, Ok(()));
339 assert_matches::assert_matches!(state.networks.get(&NETWORK_ID), None);
340
341 Ok(())
342 }
343
344 #[fuchsia::test]
345 async fn test_default_network_removal() -> Result<(), SocketProxyError> {
346 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
347 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
348 let mut state = SocketProxyState::new(fuchsia_networks);
349 const NETWORK_ID1_U64: u64 = 1;
350 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
351 const NETWORK_ID2_U64: u64 = 2;
352 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
353
354 assert_matches::assert_matches!(state.handle_default_network_removal().await, Ok(None));
357
358 assert_matches::assert_matches!(
362 state.networks.insert(
363 NETWORK_ID1,
364 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
365 NETWORK_ID1_U64
366 ),)?,
367 ),
368 None
369 );
370 assert_matches::assert_matches!(
371 state.networks.insert(
372 NETWORK_ID2,
373 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
374 NETWORK_ID2_U64
375 ),)?,
376 ),
377 None
378 );
379 state.default_id = Some(NETWORK_ID1);
380
381 let (default_network_removal_result, ()) = futures::join!(
384 state.handle_default_network_removal(),
385 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
386 );
387 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
388 assert_matches::assert_matches!(state.default_id, Some(NETWORK_ID2));
389
390 assert_matches::assert_matches!(
394 state.networks.remove(&NETWORK_ID1),
395 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
396 );
397
398 let (default_network_removal_result2, ()) = futures::join!(
401 state.handle_default_network_removal(),
402 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
403 );
404 assert_matches::assert_matches!(default_network_removal_result2, Ok(None));
405 assert_matches::assert_matches!(state.default_id, None);
406
407 Ok(())
408 }
409
410 #[fuchsia::test]
411 async fn test_multiple_operations() {
412 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
413 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
414 let mut state = SocketProxyState::new(fuchsia_networks);
415 const NETWORK_ID1_U64: u64 = 1;
416 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
417 const NETWORK_ID2_U64: u64 = 2;
418 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
419
420 let network1 = interface_properties_from_id(NETWORK_ID1_U64);
422 let (add_network_result, ()) = futures::join!(
423 state.handle_add_network(&network1),
424 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
425 );
426 assert_matches::assert_matches!(add_network_result, Ok(()));
427
428 let (set_default_network_result, ()) = futures::join!(
430 state.handle_default_network(Some(NETWORK_ID1)),
431 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
432 );
433 assert_matches::assert_matches!(set_default_network_result, Ok(()));
434
435 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
437 let (add_network_result, ()) = futures::join!(
438 state.handle_add_network(&network2),
439 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
440 );
441 assert_matches::assert_matches!(add_network_result, Ok(()));
442
443 assert_matches::assert_matches!(
446 state.handle_remove_network(NETWORK_ID1).await,
447 Err(SocketProxyError::RemovedDefaultNetwork(id))
448 if id.get() == NETWORK_ID1_U64
449 );
450
451 assert!(state.networks.get(&NETWORK_ID1).is_some());
453
454 let (default_network_removal_result, ()) = futures::join!(
456 state.handle_default_network_removal(),
457 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
458 );
459 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
460
461 assert_matches::assert_matches!(
463 state.networks.remove(&NETWORK_ID1),
464 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
465 );
466 }
467}