Skip to main content

netcfg/network/
mod.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::InterfaceId;
6use crate::dns::DNS_PORT;
7use crate::telemetry::{NetworkEventMetadata, TelemetryEvent, TelemetrySender};
8use anyhow::Context as _;
9use assert_matches::assert_matches;
10use async_utils::stream::{StreamItem, StreamWithEpitaph, Tagged, WithEpitaph as _, WithTag as _};
11use dns_server_watcher::DnsServers;
12use fidl::endpoints::{ControlHandle as _, RequestStream as _};
13use futures::StreamExt as _;
14use log::{debug, error, info, warn};
15use policy_properties::NetworkTokenExt as _;
16use std::collections::HashMap;
17use std::collections::hash_map::{Entry, OccupiedEntry};
18
19mod reachability;
20mod token_registry;
21
22use fidl_fuchsia_net as fnet;
23use fidl_fuchsia_net_ext as fnet_ext;
24use fidl_fuchsia_net_name as fnet_name;
25use fidl_fuchsia_net_policy_properties as fnp_properties;
26use fidl_fuchsia_net_reachability as freachability;
27use fidl_fuchsia_posix_socket as fposix_socket;
28use fuchsia_inspect::Property as _;
29use fuchsia_inspect_derive::{IValue, Inspect, Unit, WithInspect as _};
30pub use reachability::ReachabilityWatcherConnectionId;
31use reachability::{ReachabilityHandler, ReachabilityStream};
32
33// The id for each network, separated by network source.
34//
35// NB: These are separated in the case that the same underlying
36// interface id is used by Fuchsia and a delegated actor.
37#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub enum NetworkId {
39    Fuchsia(InterfaceId),
40    Delegated(InterfaceId),
41}
42
43impl std::fmt::Display for NetworkId {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            NetworkId::Fuchsia(interface_id) => write!(f, "fuchsia:{interface_id}"),
47            NetworkId::Delegated(interface_id) => write!(f, "delegated:{interface_id}"),
48        }
49    }
50}
51
52impl NetworkId {
53    pub fn get(&self) -> InterfaceId {
54        match self {
55            NetworkId::Fuchsia(interface_id) => *interface_id,
56            NetworkId::Delegated(interface_id) => *interface_id,
57        }
58    }
59
60    pub fn fuchsia<I: Into<InterfaceId>>(id: I) -> Self {
61        NetworkId::Fuchsia(id.into())
62    }
63
64    pub fn delegated<I: Into<InterfaceId>>(id: I) -> Self {
65        NetworkId::Delegated(id.into())
66    }
67
68    pub fn is_fuchsia(&self) -> bool {
69        matches!(self, NetworkId::Fuchsia(_))
70    }
71}
72
73// Using the interface's current v4/v6 default route properties and v4/v6 default route
74// properties provided through `fnet_interfaces_ext::Event::Changed`, determine
75// whether the interface gained or lost candidacy because of the event, or maintained
76// the same state.
77//
78// Returns None when the candidacy has not changed, Some(true) when the interface
79// gains candidacy, and Some(false) when the interface loses candidacy.
80pub(crate) fn determine_interface_state_changed(
81    prev: &fidl_fuchsia_net_interfaces::Properties,
82    curr: &fidl_fuchsia_net_interfaces_ext::Properties<
83        fidl_fuchsia_net_interfaces_ext::DefaultInterest,
84    >,
85) -> Option<bool> {
86    let was_candidate = (prev.has_default_ipv4_route.unwrap_or(curr.has_default_ipv4_route)
87        || prev.has_default_ipv6_route.unwrap_or(curr.has_default_ipv6_route))
88        && prev.online.unwrap_or(curr.online);
89    let is_candidate = (curr.has_default_ipv4_route || curr.has_default_ipv6_route) && curr.online;
90    (is_candidate != was_candidate).then_some(is_candidate)
91}
92
93#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94pub(crate) struct NetworkTokenContents {
95    network_id: NetworkId,
96    is_default: bool,
97}
98
99/// Declares a connection id and the allocator that mints it.
100///
101/// The id wraps a private counter, so the allocator is the only way to construct one outside the
102/// declaring module. Request handlers rely on that: an id that exists came from an allocator, so
103/// it is always present in the map it indexes.
104macro_rules! connection_id {
105    ($id:ident => $allocator:ident) => {
106        /// Identifies a single client connection.
107        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
108        pub struct $id(usize);
109
110        #[doc = concat!("Mints unique [`", stringify!($id), "`]s.")]
111        #[derive(Default)]
112        pub struct $allocator(usize);
113
114        impl $allocator {
115            pub fn allocate(&mut self) -> $id {
116                let id = $id(self.0);
117                self.0 += 1;
118                id
119            }
120        }
121    };
122}
123pub(crate) use connection_id;
124
125mod id {
126    // A `fuchsia.net.policy.properties.Networks` client connection.
127    connection_id!(NetworksConnectionId => NetworksConnectionIdAllocator);
128
129    // A `fuchsia.net.policy.properties.PropertyWatcher` client connection.
130    connection_id!(PropertyWatcherConnectionId => PropertyWatcherConnectionIdAllocator);
131}
132
133pub use id::{NetworksConnectionId, PropertyWatcherConnectionId};
134use id::{NetworksConnectionIdAllocator, PropertyWatcherConnectionIdAllocator};
135
136#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
137pub struct UpdateGeneration {
138    /// The current generation for `fuchsia.net.policy.properties.WatchDefault`.
139    /// Incremented each time the default network changes.
140    default_network: usize,
141
142    /// The current generation for `fuchsia.net.policy.properties.WatchProperties`.
143    /// Incremented each time a network property changes.
144    properties: usize,
145}
146
147#[derive(Clone, Debug, Default)]
148pub struct UpdateGenerations {
149    default_network: HashMap<NetworksConnectionId, usize>,
150    properties: HashMap<PropertyWatcherConnectionId, usize>,
151}
152
153impl UpdateGenerations {
154    fn default_network(&self, id: &NetworksConnectionId) -> Option<usize> {
155        self.default_network.get(id).copied()
156    }
157
158    fn set_default_network(&mut self, id: NetworksConnectionId, generation: UpdateGeneration) {
159        *self.default_network.entry(id).or_default() = generation.default_network;
160    }
161
162    fn remove_default(&mut self, id: &NetworksConnectionId) -> Option<usize> {
163        self.default_network.remove(id)
164    }
165
166    fn properties(&self, id: &PropertyWatcherConnectionId) -> Option<usize> {
167        self.properties.get(id).copied()
168    }
169
170    fn set_properties(&mut self, id: PropertyWatcherConnectionId, generation: UpdateGeneration) {
171        *self.properties.entry(id).or_default() = generation.properties;
172    }
173
174    fn remove_properties(&mut self, id: &PropertyWatcherConnectionId) -> Option<usize> {
175        self.properties.remove(id)
176    }
177}
178
179trait SetMark {
180    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>);
181}
182
183impl SetMark for fnet::Marks {
184    fn set_mark(&mut self, domain: fnet::MarkDomain, value: Option<u32>) {
185        match domain {
186            fnet::MarkDomain::Mark1 => self.mark_1 = value,
187            fnet::MarkDomain::Mark2 => self.mark_2 = value,
188        }
189    }
190}
191
192/// State for a registered `fuchsia.net.policy.properties.PropertyWatcher` client,
193/// including the network token, properties being watched, and any pending `Watch` responder.
194#[derive(Debug)]
195struct Registration {
196    token: fnp_properties::NetworkToken,
197    properties: fnp_properties::PropertyInterest,
198    responder: Option<fnp_properties::PropertyWatcherWatchResponder>,
199    /// Used to close the connection. Closing guarantees the watcher's [`ConnectionStream`]
200    /// yields the terminal item that this registration is removed on.
201    control_handle: fnp_properties::PropertyWatcherControlHandle,
202}
203
204impl Registration {
205    /// Closes the watcher's connection, with `epitaph` if one is provided.
206    ///
207    /// The registration is left in place: shutting the connection down guarantees its
208    /// [`ConnectionStream`] yields the terminal item that the registration is removed on.
209    fn close(&mut self, epitaph: Option<zx::Status>) {
210        // Shut down before releasing the responder below to communicate an epitaph
211        // when one is provided.
212        match epitaph {
213            Some(status) => self.control_handle.shutdown_with_epitaph(status),
214            None => self.control_handle.shutdown(),
215        }
216        // Drop the hanging responder, if any, so a closed watcher is no longer served.
217        self.responder = None;
218    }
219}
220
221/// State for a connected `fuchsia.net.policy.properties.Networks` client.
222#[derive(Debug)]
223struct NetworksClient {
224    /// The responder for a hanging `WatchDefault` call, if one is outstanding.
225    responder: Option<fnp_properties::NetworksWatchDefaultResponder>,
226    /// Used to close the connection. Closing guarantees the client's [`ConnectionStream`]
227    /// yields the terminal item that this entry is removed on.
228    control_handle: fnp_properties::NetworksControlHandle,
229}
230
231impl NetworksClient {
232    /// Closes the client's connection, with `epitaph` if one is provided.
233    ///
234    /// The client is left in place: shutting the connection down guarantees its
235    /// [`ConnectionStream`] yields the terminal item that the client is removed on.
236    fn close(&mut self, epitaph: Option<zx::Status>) {
237        // Shut down before releasing the responder below to communicate an epitaph
238        // when one is provided.
239        match epitaph {
240            Some(status) => self.control_handle.shutdown_with_epitaph(status),
241            None => self.control_handle.shutdown(),
242        }
243        // Drop the hanging responder, if any, so a closed client is no longer served.
244        self.responder = None;
245    }
246}
247
248#[derive(Debug, PartialEq, Default, Clone)]
249struct NetworkProperties {
250    delegated: Option<DelegatedNetworkProperties>,
251    dns_servers: Vec<fnet_name::DnsServer_>,
252    // TODO(https://fxbug.dev/567965129): Remove the following optional fields for shared
253    // Fuchsia/Starnix networks once all networks received from Starnix are required to
254    // have these fields.
255    connectivity_state: Option<fnp_properties::ConnectivityState>,
256    name: Option<String>,
257    network_type: Option<fnp_properties::NetworkType>,
258}
259
260impl NetworkProperties {
261    fn get_marks(&self) -> Option<&fnet::Marks> {
262        self.delegated.as_ref().map(|d| &d.marks)
263    }
264}
265
266impl std::fmt::Display for NetworkProperties {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        let NetworkProperties { name, network_type, connectivity_state, delegated, dns_servers } =
269            self;
270
271        if let Some(name) = name {
272            write!(f, "name={name}")?;
273        }
274        if let Some(network_type) = network_type {
275            write!(f, ", type={}", network_type.as_str())?;
276        }
277        if let Some(connectivity_state) = connectivity_state {
278            write!(f, ", state={}", connectivity_state.as_str())?;
279        }
280        if let Some(DelegatedNetworkProperties { marks, starnix_handle: _ }) = delegated {
281            write!(f, ", mark={:?}", marks.mark_1)?;
282        }
283        if delegated.is_some() || !dns_servers.is_empty() {
284            write!(f, ", dns=[")?;
285            let mut servers = dns_servers.iter().filter_map(|s| {
286                s.address.map(|a| fidl_fuchsia_net_ext::SocketAddress::from(a).0.ip().to_string())
287            });
288            if let Some(first) = servers.next() {
289                write!(f, "{first}")?;
290                for s in servers {
291                    write!(f, ", {s}")?;
292                }
293            }
294            write!(f, "]")?;
295        }
296        Ok(())
297    }
298}
299
300const NETWORK_REGISTRY_LOG_TAG: &str = "network_registry";
301
302/// The current state of all networks sent to the NetworkRegistry.
303#[derive(Default, Clone)]
304struct RegisteredNetworks {
305    /// The current default network, determined by the priority rules in
306    /// `calculate_active_default`.
307    default_network: Option<NetworkId>,
308    /// The Starnix default network, as determined by Starnix.
309    starnix_default: Option<NetworkId>,
310    networks: HashMap<NetworkId, NetworkProperties>,
311    dns_servers: Vec<fnet_name::DnsServer_>,
312}
313
314fn format_opt_network_id(id: Option<NetworkId>) -> String {
315    match id {
316        Some(id) => id.to_string(),
317        None => "None".to_string(),
318    }
319}
320
321impl RegisteredNetworks {
322    fn set_starnix_default(&mut self, next_starnix_default: Option<NetworkId>) {
323        if self.starnix_default != next_starnix_default {
324            info!(
325                tag = NETWORK_REGISTRY_LOG_TAG;
326                "SetStarnixDefault {} (prev={})",
327                format_opt_network_id(next_starnix_default),
328                format_opt_network_id(self.starnix_default),
329            );
330            self.starnix_default = next_starnix_default;
331        }
332    }
333
334    // Determine the active default network based on the starnix_default and network registry.
335    // When one or more Fuchsia networks are present, they should be prioritized over Starnix
336    // networks. The 'most prioritized' Fuchsia network is the one with the lowest ID.
337    fn calculate_active_default(&self) -> Option<NetworkId> {
338        // Note: Fuchsia networks are only added to the NetworkRegistry if they meet
339        // certain criteria (ex: have a default route and are online).
340        let first_fuchsia = self.networks.keys().filter(|id| id.is_fuchsia()).cloned().min();
341        if let Some(fd) = first_fuchsia {
342            return Some(fd);
343        }
344
345        // Fallback to starnix_default. If it is unset, no default network is available.
346        if let Some(starnix_default) = self.starnix_default {
347            // Ensure that the network is present in the network registry.
348            assert!(self.networks.contains_key(&starnix_default));
349        }
350        self.starnix_default
351    }
352
353    // Handle updates to the active default network.
354    //
355    // Returns `Some(DefaultChangedEvent)` if the new default network
356    // is different from the old one, otherwise `None`.
357    fn handle_default_network_update(&mut self) -> Option<DefaultChangedEvent> {
358        let next_default = self.calculate_active_default();
359        if next_default != self.default_network {
360            let old_default = self.default_network;
361            info!(
362                tag = NETWORK_REGISTRY_LOG_TAG;
363                "SetDefault {} (prev={})",
364                format_opt_network_id(next_default),
365                format_opt_network_id(old_default),
366            );
367            self.default_network = next_default;
368            Some(DefaultChangedEvent { previous_default: old_default })
369        } else {
370            None
371        }
372    }
373
374    fn apply(&mut self, update: NetworkRegistryUpdate) -> RegistryUpdateResult {
375        match update {
376            NetworkRegistryUpdate::UnsetDefaultNetwork => {
377                // Handle Starnix unsetting its default network.
378                self.set_starnix_default(None);
379                RegistryUpdateResult {
380                    event: UpdateApplied::None,
381                    default_changed: self.handle_default_network_update(),
382                }
383            }
384            NetworkRegistryUpdate::ChangeNetwork(network_id, network_change) => {
385                match network_change {
386                    NetworkUpdate::Properties(event) => RegistryUpdateResult {
387                        event: self.handle_changed_network(network_id, event),
388                        default_changed: self.handle_default_network_update(),
389                    },
390                    NetworkUpdate::Remove => {
391                        if self.starnix_default == Some(network_id) {
392                            error!("Cannot remove the default delegated network. Update ignored.");
393                            RegistryUpdateResult {
394                                event: UpdateApplied::None,
395                                default_changed: None,
396                            }
397                        } else if let Some(properties) = self.networks.remove(&network_id) {
398                            info!(
399                                tag = NETWORK_REGISTRY_LOG_TAG;
400                                "Remove {network_id} ({properties})"
401                            );
402                            // Elect fallback default network internally.
403                            RegistryUpdateResult {
404                                event: UpdateApplied::NetworkRemoved(network_id),
405                                default_changed: self.handle_default_network_update(),
406                            }
407                        } else {
408                            error!(
409                                "Cannot remove a non-existent network ({network_id:?}). \
410                                Update ignored."
411                            );
412                            RegistryUpdateResult {
413                                event: UpdateApplied::None,
414                                default_changed: None,
415                            }
416                        }
417                    }
418                    NetworkUpdate::MakeDefault => {
419                        match network_id {
420                            // Fuchsia networks are always the default network when present. Netcfg
421                            // does not use this API to set a Fuchsia network as the default.
422                            NetworkId::Fuchsia(_) => {}
423                            NetworkId::Delegated(_) => self.set_starnix_default(Some(network_id)),
424                        }
425                        let default_changed = self.handle_default_network_update();
426                        RegistryUpdateResult { event: UpdateApplied::None, default_changed }
427                    }
428                }
429            }
430            NetworkRegistryUpdate::UpdateDns(dns_servers) => {
431                let event = if self.dns_servers != dns_servers {
432                    self.dns_servers = dns_servers;
433                    UpdateApplied::DnsChanged
434                } else {
435                    UpdateApplied::None
436                };
437                RegistryUpdateResult { event, default_changed: None }
438            }
439        }
440    }
441
442    // Handle the `NetworkPropertiesChange` in a `NetworkRegistryUpdate`, determining
443    // whether network properties changed as a result of the update.
444    //
445    // Returns an `UpdateApplied::NetworkChanged` event if this is a valid change.
446    fn handle_changed_network(
447        &mut self,
448        network_id: NetworkId,
449        event: NetworkPropertiesChange,
450    ) -> UpdateApplied {
451        let NetworkPropertiesChange {
452            added,
453            delegated,
454            dns_servers: changed_dns_servers,
455            connectivity_state,
456            name,
457            network_type,
458        } = event;
459        let entry = self.networks.entry(network_id);
460        let result = match (added, &entry, network_id, &delegated) {
461            (true, Entry::Occupied(_), _, _) => Err("add already added network"),
462            (false, Entry::Vacant(_), _, _) => Err("update a non-added network"),
463            (_, _, NetworkId::Fuchsia(_), Some(_)) => {
464                Err("have a fuchsia network with delegated properties")
465            }
466            (_, _, NetworkId::Delegated(_), None) => {
467                Err("have a delegated network without delegated properties")
468            }
469            (_, _, NetworkId::Fuchsia(_), None) | (_, _, NetworkId::Delegated(_), Some(_)) => {
470                Ok(())
471            }
472        };
473        if let Err(e) = result {
474            error!("Cannot {e}. Update ignored.");
475            return UpdateApplied::None;
476        }
477
478        let dns_servers = changed_dns_servers.unwrap_or_default();
479        let (changed_marks, changed_dns) = match &entry {
480            Entry::Occupied(e) => (
481                e.get().get_marks() != delegated.as_ref().map(|d| &d.marks),
482                e.get().dns_servers != dns_servers,
483            ),
484            // When adding a new network, set both `changed_marks` and `changed_dns` to
485            // true so responders receive an explicit initial state.
486            Entry::Vacant(_) => (true, true),
487        };
488        let properties = NetworkProperties {
489            delegated,
490            dns_servers,
491            connectivity_state,
492            name: name.clone(),
493            network_type,
494        };
495        match &entry {
496            Entry::Occupied(e) => {
497                // Only log network updates when they have changed.
498                if *e.get() != properties {
499                    info!(
500                        tag = NETWORK_REGISTRY_LOG_TAG; "Update {network_id} ({properties})"
501                    );
502                }
503            }
504            Entry::Vacant(_) => {
505                info!(tag = NETWORK_REGISTRY_LOG_TAG; "Add {network_id} ({properties})");
506            }
507        }
508        // Validation above ensures adds (`added`) only insert into a `Vacant` entry and updates
509        // (`!added`) replace an `Occupied` entry.
510        let _: OccupiedEntry<'_, _, _> = entry.insert_entry(properties);
511        UpdateApplied::NetworkChanged {
512            network_id,
513            added,
514            changed_marks,
515            changed_dns,
516            name,
517            network_type,
518        }
519    }
520
521    /// Returns the DNS servers for the default network if it is a Fuchsia network,
522    /// otherwise returns a concatenation of DNS servers from all delegated networks.
523    /// TODO(https://fxbug.dev/428712735): Remove once dns-resolver learns about DNS
524    /// via NetworkProperties.
525    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
526        if let Some(NetworkId::Fuchsia(if_id)) = self.default_network {
527            self.networks
528                .get(&NetworkId::Fuchsia(if_id))
529                .map(|p| p.dns_servers.clone())
530                .unwrap_or_default()
531        } else {
532            self.networks
533                .iter()
534                .filter(|(id, _)| matches!(id, NetworkId::Delegated(_)))
535                .flat_map(|(_, p)| &p.dns_servers)
536                .cloned()
537                .collect()
538        }
539    }
540
541    fn dns_servers_for_network<'a>(
542        &'a self,
543        network_id: NetworkId,
544        properties: &'a NetworkProperties,
545    ) -> Vec<&'a fnet_name::DnsServer_> {
546        match network_id {
547            NetworkId::Delegated(_) => properties.dns_servers.iter().collect(),
548            NetworkId::Fuchsia(InterfaceId(if_id)) => {
549                if !properties.dns_servers.is_empty() {
550                    properties.dns_servers.iter().collect()
551                } else {
552                    self.dns_servers
553                        .iter()
554                        .filter(|s| match &s.source {
555                            Some(fnet_name::DnsServerSource::Dhcp(d)) => {
556                                d.source_interface == Some(if_id.get())
557                            }
558                            Some(fnet_name::DnsServerSource::Dhcpv6(d)) => {
559                                d.source_interface == Some(if_id.get())
560                            }
561                            Some(fnet_name::DnsServerSource::Ndp(d)) => {
562                                d.source_interface == Some(if_id.get())
563                            }
564                            _ => false,
565                        })
566                        .collect()
567                }
568            }
569        }
570    }
571
572    fn update_inspect(&self, node: &fuchsia_inspect::Node) {
573        node.clear_recorded();
574
575        if let Some(default_network) = &self.default_network {
576            node.record_string("default_network", default_network.to_string());
577        }
578
579        if let Some(starnix_default) = &self.starnix_default {
580            node.record_string("starnix_default", starnix_default.to_string());
581        }
582
583        // The cross-source set handed to the resolver, including static servers no network
584        // owns. Not `Self::consolidated_dns_servers`, the registry's SocketProxy contribution.
585        if !self.dns_servers.is_empty() {
586            node.record_child("system_dns_servers", |system_node| {
587                for (i, server) in self.dns_servers.iter().enumerate() {
588                    server.record_inspect(system_node, &i.to_string());
589                }
590            });
591        }
592
593        let mut sorted_networks: Vec<_> = self.networks.iter().collect();
594        sorted_networks.sort_by_key(|(id, _)| *id);
595
596        for (network_id, properties) in sorted_networks {
597            let network_node_name = match network_id {
598                NetworkId::Fuchsia(InterfaceId(id)) => format!("fuchsia_{id}"),
599                NetworkId::Delegated(InterfaceId(id)) => {
600                    format!("delegated_{id}")
601                }
602            };
603
604            node.record_child(network_node_name, |net_node| {
605                if let Some(name) = &properties.name {
606                    net_node.record_string("name", name);
607                }
608                if let Some(network_type) = &properties.network_type {
609                    net_node.record_string("network_type", network_type.as_str());
610                }
611                if let Some(connectivity_state) = &properties.connectivity_state {
612                    net_node.record_string("connectivity_state", connectivity_state.as_str());
613                }
614
615                let network_dns_servers = self.dns_servers_for_network(*network_id, properties);
616
617                if !network_dns_servers.is_empty() {
618                    net_node.record_child("dns_servers", |dns_node| {
619                        for (i, server) in network_dns_servers.iter().enumerate() {
620                            server.record_inspect(dns_node, &i.to_string());
621                        }
622                    });
623                }
624            });
625        }
626    }
627}
628
629trait DnsServerInspectExt {
630    fn record_inspect(&self, node: &fuchsia_inspect::Node, name: &str);
631}
632
633impl DnsServerInspectExt for fnet_name::DnsServer_ {
634    fn record_inspect(&self, node: &fuchsia_inspect::Node, name: &str) {
635        let Some(address) = &self.address else {
636            return;
637        };
638        let server_str = fnet_ext::SocketAddress::from(*address).to_string();
639        let (source_type, iface_id) = dns_source_info(&self.source);
640
641        node.record_child(name, |entry_node| {
642            entry_node.record_string("server", server_str);
643            entry_node.record_string("type", source_type);
644            if let Some(iface_id) = iface_id {
645                entry_node.record_uint("interface_id", iface_id);
646            }
647        });
648    }
649}
650
651trait NetworkTypeInspectExt {
652    fn as_str(&self) -> &'static str;
653}
654
655impl NetworkTypeInspectExt for fnp_properties::NetworkType {
656    fn as_str(&self) -> &'static str {
657        match self {
658            fnp_properties::NetworkType::Unknown => "Unknown",
659            fnp_properties::NetworkType::Ethernet => "Ethernet",
660            fnp_properties::NetworkType::Wifi => "Wifi",
661            fnp_properties::NetworkType::Bluetooth => "Bluetooth",
662            fnp_properties::NetworkType::Cellular => "Cellular",
663            fnp_properties::NetworkType::__SourceBreaking { .. } => "Unknown",
664        }
665    }
666}
667
668trait ConnectivityStateInspectExt {
669    fn as_str(&self) -> &'static str;
670}
671
672impl ConnectivityStateInspectExt for fnp_properties::ConnectivityState {
673    fn as_str(&self) -> &'static str {
674        match self {
675            fnp_properties::ConnectivityState::NoConnectivity => "NoConnectivity",
676            fnp_properties::ConnectivityState::LocalConnectivity => "LocalConnectivity",
677            fnp_properties::ConnectivityState::PartialConnectivity => "PartialConnectivity",
678            fnp_properties::ConnectivityState::FullConnectivity => "FullConnectivity",
679            fnp_properties::ConnectivityState::__SourceBreaking { .. } => "Unknown",
680        }
681    }
682}
683
684fn dns_source_info(source: &Option<fnet_name::DnsServerSource>) -> (&'static str, Option<u64>) {
685    match source {
686        Some(fnet_name::DnsServerSource::StaticSource(_)) => ("Static", None),
687        Some(fnet_name::DnsServerSource::Dhcp(dhcp)) => ("Dhcpv4", dhcp.source_interface),
688        Some(fnet_name::DnsServerSource::Dhcpv6(dhcpv6)) => ("Dhcpv6", dhcpv6.source_interface),
689        Some(fnet_name::DnsServerSource::Ndp(ndp)) => ("Ndp", ndp.source_interface),
690        Some(fnet_name::DnsServerSource::SocketProxy(_)) => ("SocketProxy", None),
691        Some(fnet_name::DnsServerSource::__SourceBreaking { .. }) | None => ("Unknown", None),
692    }
693}
694
695/// Helper trait for building property update lists based on a client's registration.
696trait PropertyUpdates {
697    fn add_socket_marks(
698        &mut self,
699        network_registry: &RegisteredNetworks,
700        network: &NetworkTokenContents,
701        watched_properties: fnp_properties::PropertyInterest,
702    );
703    fn add_dns(
704        &mut self,
705        network_registry: &RegisteredNetworks,
706        network: &NetworkTokenContents,
707        watched_properties: fnp_properties::PropertyInterest,
708    );
709}
710
711impl PropertyUpdates for fnp_properties::PropertyUpdate {
712    fn add_socket_marks(
713        &mut self,
714        network_registry: &RegisteredNetworks,
715        network: &NetworkTokenContents,
716        watched_properties: fnp_properties::PropertyInterest,
717    ) {
718        if !watched_properties.contains(fnp_properties::PropertyInterest::SOCKET_MARKS) {
719            return;
720        }
721
722        match network_registry.networks.get(&network.network_id) {
723            Some(network) => {
724                if let Some(socket_marks) = network.get_marks() {
725                    self.socket_marks = Some(socket_marks.clone());
726                }
727                return;
728            }
729            None => {
730                error!(
731                    "State is inconsistent. We attempted to add marks for a \
732            network that is not known: {:?}",
733                    network.network_id
734                );
735            }
736        }
737    }
738
739    fn add_dns(
740        &mut self,
741        network_registry: &RegisteredNetworks,
742        network: &NetworkTokenContents,
743        watched_properties: fnp_properties::PropertyInterest,
744    ) {
745        if !watched_properties.contains(fnp_properties::PropertyInterest::DNS_CONFIGURATION) {
746            return;
747        }
748
749        let interface_id = network.network_id;
750        self.dns_configuration = Some(fnp_properties::DnsConfiguration {
751            servers: Some(
752                network_registry
753                    .dns_servers
754                    .iter()
755                    .filter(|d| {
756                        match &d.source {
757                            Some(source) => match source {
758                                fnet_name::DnsServerSource::StaticSource(_) => true,
759                                // `extract_dns_servers` prefers IPv4 DNS
760                                // over IPv6 DNS when DNS servers are
761                                // provided by the SocketProxy.
762                                fnet_name::DnsServerSource::SocketProxy(
763                                    fnet_name::SocketProxyDnsServerSource {
764                                        source_interface, ..
765                                    },
766                                ) => match (interface_id, source_interface) {
767                                    (_, None) => true,
768                                    (id1, Some(id2)) => {
769                                        Ok(id1)
770                                            == InterfaceId::try_from(*id2)
771                                                .map(|id| NetworkId::delegated(id))
772                                    }
773                                },
774                                fnet_name::DnsServerSource::Dhcp(
775                                    fnet_name::DhcpDnsServerSource { source_interface, .. },
776                                )
777                                | fnet_name::DnsServerSource::Ndp(
778                                    fnet_name::NdpDnsServerSource { source_interface, .. },
779                                )
780                                | fnet_name::DnsServerSource::Dhcpv6(
781                                    fnet_name::Dhcpv6DnsServerSource { source_interface, .. },
782                                ) => match (interface_id, source_interface) {
783                                    (_, None) => true,
784                                    (id1, Some(id2)) => {
785                                        Ok(id1)
786                                            == InterfaceId::try_from(*id2)
787                                                .map(|id| NetworkId::fuchsia(id))
788                                    }
789                                },
790
791                                _ => {
792                                    error!("unhandled DnsServerSource: {source:?}");
793                                    false
794                                }
795                            },
796
797                            // No source, assume static source, so include it.
798                            None => true,
799                        }
800                    })
801                    .cloned()
802                    .collect::<Vec<_>>(),
803            ),
804            ..Default::default()
805        });
806    }
807}
808
809/// Delegated network specific properties.
810///
811/// Neither `marks` nor `starnix_handle` is expected to change for an existing
812/// network, but since they are not managed by Fuchsia we update them on
813/// `NetworkRegistry.Update` to guard against any assumptions about their
814/// immutability. Updating `starnix_handle` only affects which handle future
815/// `PrivilegedNetworkLookup.Lookup` calls match; any `NetworkToken` already
816/// minted for the network is keyed by `NetworkId` and remains active until the
817/// network itself is removed.
818#[derive(Clone, Debug, Default, PartialEq)]
819pub struct DelegatedNetworkProperties {
820    /// The socket marks for the delegated network.
821    pub marks: fnet::Marks,
822    /// The Starnix network handle.
823    pub starnix_handle: u64,
824}
825
826/// An event representing the properties that changed for a network.
827#[derive(Clone, Debug, Default)]
828pub struct NetworkPropertiesChange {
829    /// When true, this is a new network being added. Otherwise, this is an
830    /// update to an existing network.
831    pub added: bool,
832    /// Properties specific to delegated networks (`None` for Fuchsia networks).
833    pub delegated: Option<DelegatedNetworkProperties>,
834    /// If present, contains the new DNS servers for this network.
835    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
836    /// The new connectivity state of the network.
837    pub connectivity_state: Option<fnp_properties::ConnectivityState>,
838    /// The name of the network.
839    pub name: Option<String>,
840    /// The transport type of the network.
841    pub network_type: Option<fnp_properties::NetworkType>,
842}
843
844#[derive(Debug, Clone)]
845pub enum NetworkUpdate {
846    /// Change a network's properties.
847    Properties(NetworkPropertiesChange),
848    Remove,
849    MakeDefault,
850}
851
852#[derive(Debug, PartialEq, Eq, Clone)]
853struct DefaultChangedEvent {
854    previous_default: Option<NetworkId>,
855}
856
857#[derive(Debug, PartialEq, Eq)]
858struct RegistryUpdateResult {
859    event: UpdateApplied,
860    /// Stores whether the default network has changed, and the previous default
861    /// network, if any.
862    default_changed: Option<DefaultChangedEvent>,
863}
864
865#[derive(Debug, PartialEq, Eq, Clone)]
866enum UpdateApplied {
867    /// No update was performed.
868    None,
869
870    /// Whether the DNS servers changed.
871    DnsChanged,
872
873    /// Network was added or updated, contains the NetworkId of the added network.
874    NetworkChanged {
875        network_id: NetworkId,
876        added: bool,
877        changed_marks: bool,
878        changed_dns: bool,
879        name: Option<String>,
880        network_type: Option<fnp_properties::NetworkType>,
881    },
882
883    /// Network was removed, contains the NetworkId of the removed network.
884    NetworkRemoved(NetworkId),
885}
886
887#[derive(Debug, Clone)]
888pub enum NetworkRegistryUpdate {
889    UnsetDefaultNetwork,
890    ChangeNetwork(NetworkId, NetworkUpdate),
891    UpdateDns(Vec<fnet_name::DnsServer_>),
892}
893
894impl NetworkRegistryUpdate {
895    pub fn unset_default() -> Self {
896        NetworkRegistryUpdate::UnsetDefaultNetwork
897    }
898
899    pub fn dns(dns_servers: &DnsServers) -> Self {
900        // TODO(https://fxbug.dev/477980011): Switch to deriving dns servers from
901        // NetworkRegistry updates.
902        NetworkRegistryUpdate::UpdateDns(dns_servers.consolidated_dns_servers())
903    }
904}
905
906/// A validated copy of [`fnp_properties::Network`] ensuring required fields
907/// are present for delegated (Starnix) networks.
908///
909/// Netcfg discovers native Fuchsia networks internally from Netstack, so
910/// external `NetworkRegistry` requests handled by this type are strictly
911/// delegated networks registered by runtimes like Starnix.
912struct ValidatedNetwork {
913    network_id: NetworkId,
914    delegated: DelegatedNetworkProperties,
915    dns_servers: Vec<fnet_name::DnsServer_>,
916    connectivity: Option<fnp_properties::ConnectivityState>,
917    name: Option<String>,
918    network_type: Option<fnp_properties::NetworkType>,
919}
920
921#[derive(Debug, PartialEq, Eq)]
922enum NetworkValidationError {
923    MissingNetworkId,
924    MissingNetworkInfo,
925}
926
927impl From<NetworkValidationError> for fnp_properties::NetworkRegistryAddError {
928    fn from(error: NetworkValidationError) -> Self {
929        match error {
930            NetworkValidationError::MissingNetworkId => Self::MissingNetworkId,
931            NetworkValidationError::MissingNetworkInfo => Self::MissingNetworkInfo,
932        }
933    }
934}
935
936impl From<NetworkValidationError> for fnp_properties::NetworkRegistryUpdateError {
937    fn from(error: NetworkValidationError) -> Self {
938        match error {
939            NetworkValidationError::MissingNetworkId => Self::MissingNetworkId,
940            NetworkValidationError::MissingNetworkInfo => Self::MissingNetworkInfo,
941        }
942    }
943}
944
945impl TryFrom<fnp_properties::Network> for ValidatedNetwork {
946    type Error = NetworkValidationError;
947
948    fn try_from(network: fnp_properties::Network) -> Result<Self, Self::Error> {
949        let raw_network_id = network.network_id.ok_or(NetworkValidationError::MissingNetworkId)?;
950        let network_id = InterfaceId::try_from(raw_network_id)
951            .map(NetworkId::delegated)
952            .map_err(|_| NetworkValidationError::MissingNetworkId)?;
953        let Some(fnp_properties::NetworkInfo::Starnix(info)) = network.info else {
954            return Err(NetworkValidationError::MissingNetworkInfo);
955        };
956        let starnix_handle = info.handle.ok_or(NetworkValidationError::MissingNetworkInfo)?;
957
958        let mut marks = fnet::Marks::default();
959        marks.set_mark(fnet::MARK_DOMAIN_SO_MARK, info.mark);
960
961        let dns_servers =
962            NetpolNetworksService::extract_dns_servers(&network.dns_servers, raw_network_id.into());
963
964        Ok(Self {
965            network_id,
966            delegated: DelegatedNetworkProperties { marks, starnix_handle },
967            dns_servers,
968            connectivity: network.connectivity,
969            name: network.name,
970            network_type: network.network_type,
971        })
972    }
973}
974
975/// The result of a delegated network update.
976///
977/// Returned to the main event loop to propagate system-wide configuration
978/// changes (such as DNS server updates) and notify active watchers.
979#[derive(Debug, Default, PartialEq)]
980pub struct DelegatedNetworkUpdateResult {
981    /// If present, contains the new consolidated DNS servers known by the
982    /// network registry.
983    pub dns_servers: Option<Vec<fnet_name::DnsServer_>>,
984}
985
986/// A public wrapper enum for FIDL request streams accepted by
987/// [`NetpolNetworksService::add_stream`].
988///
989/// This type represents incoming streams before they are attached to the service's event loop.
990pub enum NetworkRequestStream {
991    Networks(fnp_properties::NetworksRequestStream),
992    NetworkTokenResolver(fnp_properties::NetworkTokenResolverRequestStream),
993    PrivilegedNetworkLookup(fnp_properties::PrivilegedNetworkLookupRequestStream),
994    PropertyWatcher {
995        connection_id: PropertyWatcherConnectionId,
996        stream: fnp_properties::PropertyWatcherRequestStream,
997    },
998    DelegatedNetworks(fnp_properties::NetworkRegistryRequestStream),
999    Reachability(freachability::MonitorRequestStream),
1000}
1001
1002impl From<fnp_properties::NetworksRequestStream> for NetworkRequestStream {
1003    fn from(s: fnp_properties::NetworksRequestStream) -> Self {
1004        Self::Networks(s)
1005    }
1006}
1007impl From<fnp_properties::NetworkTokenResolverRequestStream> for NetworkRequestStream {
1008    fn from(s: fnp_properties::NetworkTokenResolverRequestStream) -> Self {
1009        Self::NetworkTokenResolver(s)
1010    }
1011}
1012
1013impl From<fnp_properties::PrivilegedNetworkLookupRequestStream> for NetworkRequestStream {
1014    fn from(s: fnp_properties::PrivilegedNetworkLookupRequestStream) -> Self {
1015        Self::PrivilegedNetworkLookup(s)
1016    }
1017}
1018
1019impl From<fnp_properties::NetworkRegistryRequestStream> for NetworkRequestStream {
1020    fn from(s: fnp_properties::NetworkRegistryRequestStream) -> Self {
1021        Self::DelegatedNetworks(s)
1022    }
1023}
1024
1025impl From<freachability::MonitorRequestStream> for NetworkRequestStream {
1026    fn from(s: freachability::MonitorRequestStream) -> Self {
1027        Self::Reachability(s)
1028    }
1029}
1030
1031impl From<(PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream)>
1032    for NetworkRequestStream
1033{
1034    fn from(
1035        s: (PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream),
1036    ) -> Self {
1037        let (connection_id, stream) = s;
1038        Self::PropertyWatcher { connection_id, stream }
1039    }
1040}
1041
1042/// A client request stream tagged with its connection id, ending in an epitaph carrying that id.
1043///
1044/// The epitaph is what the event loop cleans up on; without it [`futures::stream::SelectAll`]
1045/// would drop the finished stream silently and the connection's state would leak.
1046pub(crate) type ConnectionStream<Id, S> = StreamWithEpitaph<Tagged<Id, S>, Id>;
1047
1048/// Flattens an item from a [`ConnectionStream`] into its connection id and the request, where
1049/// `None` marks the end of the connection.
1050fn split_connection_item<Id, T>(item: StreamItem<(Id, T), Id>) -> (Id, Option<T>) {
1051    match item {
1052        StreamItem::Item((id, request)) => (id, Some(request)),
1053        StreamItem::Epitaph(id) => (id, None),
1054    }
1055}
1056
1057/// The single-client `NetworkRegistry` request stream, ending in an epitaph so the
1058/// event loop observes when the client disconnects.
1059type DelegatedNetworksStream = StreamWithEpitaph<fnp_properties::NetworkRegistryRequestStream, ()>;
1060
1061/// An internal wrapper enum for active FIDL request streams stored in
1062/// [`NetpolNetworksService::streams`].
1063enum NetworkRequestStreamInner {
1064    Networks(ConnectionStream<NetworksConnectionId, fnp_properties::NetworksRequestStream>),
1065    NetworkTokenResolver(fnp_properties::NetworkTokenResolverRequestStream),
1066    PrivilegedNetworkLookup(fnp_properties::PrivilegedNetworkLookupRequestStream),
1067    PropertyWatcher(
1068        ConnectionStream<PropertyWatcherConnectionId, fnp_properties::PropertyWatcherRequestStream>,
1069    ),
1070    DelegatedNetworks(DelegatedNetworksStream),
1071    Reachability(ReachabilityStream),
1072}
1073
1074impl futures::Stream for NetworkRequestStreamInner {
1075    type Item = NetworkRequest;
1076
1077    fn poll_next(
1078        mut self: std::pin::Pin<&mut Self>,
1079        cx: &mut std::task::Context<'_>,
1080    ) -> std::task::Poll<Option<Self::Item>> {
1081        match *self {
1082            NetworkRequestStreamInner::Networks(ref mut stream) => {
1083                stream.poll_next_unpin(cx).map(|o| {
1084                    o.map(split_connection_item)
1085                        .map(|(id, request)| NetworkAttributesRequest { id, request })
1086                        .map(NetworkRequest::NetworkAttributes)
1087                })
1088            }
1089            NetworkRequestStreamInner::NetworkTokenResolver(ref mut stream) => {
1090                stream.poll_next_unpin(cx).map(|o| {
1091                    o.map(|request| NetworkTokenResolverRequest { request })
1092                        .map(NetworkRequest::NetworkTokenResolver)
1093                })
1094            }
1095            NetworkRequestStreamInner::PrivilegedNetworkLookup(ref mut stream) => {
1096                stream.poll_next_unpin(cx).map(|o| {
1097                    o.map(|request| PrivilegedNetworkLookupRequest { request })
1098                        .map(NetworkRequest::PrivilegedNetworkLookup)
1099                })
1100            }
1101            NetworkRequestStreamInner::PropertyWatcher(ref mut stream) => {
1102                stream.poll_next_unpin(cx).map(|o| {
1103                    o.map(split_connection_item)
1104                        .map(|(id, request)| PropertyWatcherRequest { id, request })
1105                        .map(NetworkRequest::PropertyWatcher)
1106                })
1107            }
1108            NetworkRequestStreamInner::DelegatedNetworks(ref mut stream) => {
1109                stream.poll_next_unpin(cx).map(|o| {
1110                    o.map(|item| match item {
1111                        StreamItem::Item(request) => {
1112                            DelegatedNetworksRequest { request: Some(request) }
1113                        }
1114                        StreamItem::Epitaph(()) => DelegatedNetworksRequest { request: None },
1115                    })
1116                    .map(NetworkRequest::DelegatedNetworks)
1117                })
1118            }
1119            NetworkRequestStreamInner::Reachability(ref mut stream) => {
1120                stream.poll_next_unpin(cx).map(|o| {
1121                    o.map(split_connection_item)
1122                        .map(|(id, request)| ReachabilityRequest { id, request })
1123                        .map(NetworkRequest::Reachability)
1124                })
1125            }
1126        }
1127    }
1128}
1129
1130/// A wrapper for [`fnp_properties::NetworksRequest`] that includes the
1131/// [`NetworksConnectionId`] of the connection that sent the request, or `None` if the client
1132/// connection closed.
1133pub struct NetworkAttributesRequest {
1134    pub id: NetworksConnectionId,
1135    pub request: Option<Result<fnp_properties::NetworksRequest, fidl::Error>>,
1136}
1137
1138/// A wrapper for [`fnp_properties::NetworkTokenResolverRequest`].
1139pub struct NetworkTokenResolverRequest {
1140    pub request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
1141}
1142
1143/// A wrapper for [`fnp_properties::PrivilegedNetworkLookupRequest`].
1144pub struct PrivilegedNetworkLookupRequest {
1145    pub request: Result<fnp_properties::PrivilegedNetworkLookupRequest, fidl::Error>,
1146}
1147
1148/// A wrapper for [`fnp_properties::PropertyWatcherRequest`] that includes the
1149/// [`PropertyWatcherConnectionId`] of the connection that sent the request, or `None` if the
1150/// client connection closed.
1151pub struct PropertyWatcherRequest {
1152    pub id: PropertyWatcherConnectionId,
1153    pub request: Option<Result<fnp_properties::PropertyWatcherRequest, fidl::Error>>,
1154}
1155
1156/// A wrapper for [`fnp_properties::NetworkRegistryRequest`], or `None` if the client
1157/// connection closed.
1158pub struct DelegatedNetworksRequest {
1159    pub request: Option<Result<fnp_properties::NetworkRegistryRequest, fidl::Error>>,
1160}
1161
1162/// A wrapper for [`freachability::MonitorRequest`] that includes the
1163/// [`ReachabilityWatcherConnectionId`] and indicates whether the watcher stream is still open.
1164pub struct ReachabilityRequest {
1165    pub id: ReachabilityWatcherConnectionId,
1166    /// Note: Storing the Request inside the Option allows us to distinguish between the channel
1167    /// being closed and a Watch request being sent to support cleaner channel-closing logic.
1168    pub request: Option<Result<freachability::MonitorRequest, fidl::Error>>,
1169}
1170
1171/// An enum representing all possible events that can be received by the NetpolNetworksService
1172/// event loop.
1173pub enum NetworkRequest {
1174    NetworkAttributes(NetworkAttributesRequest),
1175    NetworkTokenResolver(NetworkTokenResolverRequest),
1176    PrivilegedNetworkLookup(PrivilegedNetworkLookupRequest),
1177    PropertyWatcher(PropertyWatcherRequest),
1178    DelegatedNetworks(DelegatedNetworksRequest),
1179    Reachability(ReachabilityRequest),
1180}
1181
1182impl futures::Stream for NetpolNetworksService {
1183    type Item = NetworkRequest;
1184
1185    fn poll_next(
1186        mut self: std::pin::Pin<&mut Self>,
1187        cx: &mut std::task::Context<'_>,
1188    ) -> std::task::Poll<Option<Self::Item>> {
1189        self.streams.poll_next_unpin(cx)
1190    }
1191}
1192
1193impl futures::stream::FusedStream for NetpolNetworksService {
1194    fn is_terminated(&self) -> bool {
1195        self.streams.is_terminated()
1196    }
1197}
1198
1199#[derive(Unit, Debug, Default, Clone)]
1200struct MethodInspect {
1201    successes: u32,
1202    errors: u32,
1203}
1204
1205#[derive(Unit, Debug, Default, Clone)]
1206struct RegistryMetrics {
1207    adds: MethodInspect,
1208    removes: MethodInspect,
1209    /// Counts invocations of the `SetDefault` FIDL method. Both setting a
1210    /// default network ID and unsetting the default network are
1211    /// valid operations that count as successes.
1212    set_defaults: MethodInspect,
1213    updates: MethodInspect,
1214}
1215
1216#[derive(Inspect, Default)]
1217struct OperationsMetrics {
1218    delegated: IValue<RegistryMetrics>,
1219    inspect_node: fuchsia_inspect::Node,
1220}
1221
1222impl OperationsMetrics {
1223    fn record_set_default(
1224        &mut self,
1225        result: &Result<(), fnp_properties::NetworkRegistrySetDefaultError>,
1226    ) {
1227        let mut delegated = self.delegated.as_mut();
1228        match result {
1229            Ok(()) => delegated.set_defaults.successes += 1,
1230            Err(_) => delegated.set_defaults.errors += 1,
1231        }
1232    }
1233
1234    fn record_add(&mut self, result: &Result<(), fnp_properties::NetworkRegistryAddError>) {
1235        let mut delegated = self.delegated.as_mut();
1236        match result {
1237            Ok(()) => delegated.adds.successes += 1,
1238            Err(_) => delegated.adds.errors += 1,
1239        }
1240    }
1241
1242    fn record_update(&mut self, result: &Result<(), fnp_properties::NetworkRegistryUpdateError>) {
1243        let mut delegated = self.delegated.as_mut();
1244        match result {
1245            Ok(()) => delegated.updates.successes += 1,
1246            Err(_) => delegated.updates.errors += 1,
1247        }
1248    }
1249
1250    fn record_remove(&mut self, result: &Result<(), fnp_properties::NetworkRegistryRemoveError>) {
1251        let mut delegated = self.delegated.as_mut();
1252        match result {
1253            Ok(()) => delegated.removes.successes += 1,
1254            Err(_) => delegated.removes.errors += 1,
1255        }
1256    }
1257}
1258
1259/// Name of the Inspect property recording how many networks are registered.
1260const NETWORK_COUNT_PROPERTY_NAME: &str = "network_count";
1261
1262#[derive(Default)]
1263pub struct NetpolNetworksService {
1264    // The current generation
1265    current_generation: UpdateGeneration,
1266    // The last generation sent per connection
1267    generations_by_connection: UpdateGenerations,
1268    // Networks connections
1269    networks_clients: HashMap<NetworksConnectionId, NetworksClient>,
1270    // The active `fuchsia.net.policy.properties.NetworkRegistry` client connection, if any.
1271    delegated_networks_client: Option<fnp_properties::NetworkRegistryControlHandle>,
1272    tokens: token_registry::TokenRegistry<NetworkTokenContents>,
1273    // NetworkProperty Watchers
1274    property_watchers: HashMap<PropertyWatcherConnectionId, Registration>,
1275    // The networks known to the system
1276    network_registry: RegisteredNetworks,
1277    telemetry: Option<TelemetrySender>,
1278    // Mints ids for networks connections
1279    next_networks_id: NetworksConnectionIdAllocator,
1280    // Mints ids for property watcher connections
1281    next_watcher_id: PropertyWatcherConnectionIdAllocator,
1282    // Reachability Monitor Watcher Handler
1283    reachability_handler: ReachabilityHandler,
1284    // The multiplexed stream of events handled by the eventloop
1285    streams: futures::stream::SelectAll<NetworkRequestStreamInner>,
1286
1287    // Inspect metrics for operations
1288    metrics: OperationsMetrics,
1289    // Inspect node for network topology & DNS servers
1290    networks_inspect_node: Option<fuchsia_inspect::Node>,
1291    // The number of registered networks. Recorded alongside the network
1292    // registry node rather than within it so that a registry that holds no
1293    // networks can be told apart from one whose contents are missing.
1294    network_count: Option<fuchsia_inspect::UintProperty>,
1295}
1296
1297impl NetpolNetworksService {
1298    pub fn with_operations_inspect(
1299        mut self,
1300        parent: &fuchsia_inspect::Node,
1301        name: impl AsRef<str>,
1302    ) -> Result<Self, fuchsia_inspect_derive::AttachError> {
1303        self.metrics = OperationsMetrics::default().with_inspect(parent, name)?;
1304        Ok(self)
1305    }
1306
1307    pub fn with_network_registry_inspect(
1308        mut self,
1309        parent: &fuchsia_inspect::Node,
1310        name: impl AsRef<str>,
1311    ) -> Self {
1312        self.networks_inspect_node = Some(parent.create_child(name.as_ref()));
1313        self.network_count = Some(parent.create_uint(NETWORK_COUNT_PROPERTY_NAME, 0));
1314        self.update_network_inspect();
1315        self
1316    }
1317
1318    /// Records the current contents of the network registry to Inspect.
1319    fn update_network_inspect(&self) {
1320        if let Some(networks_node) = &self.networks_inspect_node {
1321            self.network_registry.update_inspect(networks_node);
1322        }
1323        if let Some(network_count) = &self.network_count {
1324            network_count.set(self.network_registry.networks.len() as u64);
1325        }
1326    }
1327
1328    pub fn with_inspect(
1329        self,
1330        telemetry_parent: &fuchsia_inspect::Node,
1331        operations_name: impl AsRef<str>,
1332        networks_parent: &fuchsia_inspect::Node,
1333        networks_name: impl AsRef<str>,
1334    ) -> Result<Self, fuchsia_inspect_derive::AttachError> {
1335        let service = self.with_operations_inspect(telemetry_parent, operations_name)?;
1336        Ok(service.with_network_registry_inspect(networks_parent, networks_name))
1337    }
1338
1339    pub fn synthesize_reachability_snapshot(&self) -> freachability::Snapshot {
1340        ReachabilityHandler::synthesize_snapshot(
1341            self.network_registry
1342                .default_network
1343                .and_then(|id| self.network_registry.networks.get(&id)),
1344        )
1345    }
1346
1347    fn maybe_notify_watchers(&mut self) {
1348        let current_snapshot = self.synthesize_reachability_snapshot();
1349        self.reachability_handler.maybe_notify_watchers(&current_snapshot);
1350    }
1351
1352    fn handle_reachability_request(
1353        &mut self,
1354        id: ReachabilityWatcherConnectionId,
1355        request: Option<Result<freachability::MonitorRequest, fidl::Error>>,
1356    ) {
1357        let current_snapshot = self.synthesize_reachability_snapshot();
1358        self.reachability_handler.handle_request(&current_snapshot, id, request);
1359    }
1360
1361    #[cfg(test)]
1362    pub fn reachability_watcher_count(&self) -> usize {
1363        self.reachability_handler.watcher_count()
1364    }
1365
1366    pub fn set_telemetry(&mut self, telemetry: TelemetrySender) {
1367        self.telemetry = Some(telemetry);
1368    }
1369
1370    pub fn add_stream<S: Into<NetworkRequestStream>>(&mut self, s: S) {
1371        match s.into() {
1372            NetworkRequestStream::Networks(stream) => {
1373                let id = self.next_networks_id.allocate();
1374                let previous = self.networks_clients.insert(
1375                    id,
1376                    NetworksClient { responder: None, control_handle: stream.control_handle() },
1377                );
1378                assert!(previous.is_none(), "networks client {id:?} is already registered");
1379                self.streams
1380                    .push(NetworkRequestStreamInner::Networks(stream.tagged(id).with_epitaph(id)));
1381            }
1382            NetworkRequestStream::NetworkTokenResolver(stream) => {
1383                self.streams.push(NetworkRequestStreamInner::NetworkTokenResolver(stream));
1384            }
1385            NetworkRequestStream::PrivilegedNetworkLookup(stream) => {
1386                self.streams.push(NetworkRequestStreamInner::PrivilegedNetworkLookup(stream));
1387            }
1388            NetworkRequestStream::DelegatedNetworks(stream) => {
1389                if self.delegated_networks_client.is_some() {
1390                    warn!(
1391                        "Only one connection to \
1392                        fuchsia.net.policy.properties/NetworkRegistry is allowed at a time"
1393                    );
1394                    stream.control_handle().shutdown_with_epitaph(zx::Status::ALREADY_EXISTS);
1395                } else {
1396                    self.delegated_networks_client = Some(stream.control_handle());
1397                    self.streams.push(NetworkRequestStreamInner::DelegatedNetworks(
1398                        stream.with_epitaph(()),
1399                    ));
1400                }
1401            }
1402            NetworkRequestStream::PropertyWatcher { connection_id, stream } => {
1403                self.streams.push(NetworkRequestStreamInner::PropertyWatcher(
1404                    stream.tagged(connection_id).with_epitaph(connection_id),
1405                ));
1406            }
1407            NetworkRequestStream::Reachability(stream) => {
1408                if let Some(reachability_stream) = self.reachability_handler.add_stream(stream) {
1409                    self.streams.push(NetworkRequestStreamInner::Reachability(reachability_stream));
1410                }
1411            }
1412        }
1413    }
1414
1415    pub async fn handle_event(
1416        &mut self,
1417        event: NetworkRequest,
1418    ) -> Result<DelegatedNetworkUpdateResult, anyhow::Error> {
1419        match event {
1420            NetworkRequest::NetworkAttributes(NetworkAttributesRequest { id, request }) => {
1421                self.handle_network_attributes_request(id, request).await?;
1422                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1423            }
1424            NetworkRequest::NetworkTokenResolver(NetworkTokenResolverRequest { request }) => {
1425                self.handle_network_token_resolver_request(request).await?;
1426                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1427            }
1428            NetworkRequest::PrivilegedNetworkLookup(PrivilegedNetworkLookupRequest { request }) => {
1429                self.handle_privileged_network_lookup_request(request)?;
1430                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1431            }
1432            NetworkRequest::DelegatedNetworks(DelegatedNetworksRequest { request }) => {
1433                Ok(self.handle_delegated_networks_update(request).await)
1434            }
1435            NetworkRequest::PropertyWatcher(PropertyWatcherRequest { id, request }) => {
1436                self.handle_property_watcher_request(id, request).await?;
1437                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1438            }
1439            NetworkRequest::Reachability(ReachabilityRequest { id, request }) => {
1440                self.handle_reachability_request(id, request);
1441                Ok(DelegatedNetworkUpdateResult { dns_servers: None })
1442            }
1443        }
1444    }
1445
1446    /// Returns the consolidated DNS servers from the Network Registry.
1447    pub fn consolidated_dns_servers(&self) -> Vec<fnet_name::DnsServer_> {
1448        self.network_registry.consolidated_dns_servers()
1449    }
1450
1451    /// Handles a single item produced by a client's [`NetworksRequestStream`].
1452    ///
1453    /// # Panics
1454    ///
1455    /// Panics if `id` does not correspond to a currently registered client. A client is removed
1456    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1457    /// item from the stream belongs to a live client.
1458    pub async fn handle_network_attributes_request(
1459        &mut self,
1460        id: NetworksConnectionId,
1461        req: Option<Result<fnp_properties::NetworksRequest, fidl::Error>>,
1462    ) -> Result<(), anyhow::Error> {
1463        let mut entry = assert_matches!(
1464            self.networks_clients.entry(id),
1465            Entry::Occupied(entry) => entry,
1466            "request for unknown networks client {id:?}"
1467        );
1468
1469        let req = match req {
1470            Some(Ok(req)) => req,
1471            Some(Err(e)) => {
1472                // A clean disconnect yields the terminal item below rather than an error, so
1473                // this is always abnormal: a malformed request or a channel-level read failure.
1474                // No epitaph is sent because the channel may no longer be writable.
1475                error!("Networks client {id:?} stream error: {e}");
1476                entry.get_mut().close(None);
1477                return Ok(());
1478            }
1479            // The client's stream has terminated, either because the client went away or
1480            // because of a `close` above. This is the only place clients are removed.
1481            None => {
1482                let _: NetworksClient = entry.remove();
1483                let _: Option<_> = self.generations_by_connection.remove_default(&id);
1484                return Ok(());
1485            }
1486        };
1487        match req {
1488            fnp_properties::NetworksRequest::WatchDefault { responder } => {
1489                let client = entry.get_mut();
1490                if client.responder.is_some() {
1491                    warn!(
1492                        "Only one call to fuchsia.net.policy.properties/Networks.WatchDefault \
1493                          may be active per connection"
1494                    );
1495                    // Close the connection; the terminal item performs the cleanup.
1496                    client.close(Some(zx::Status::ALREADY_EXISTS));
1497                } else {
1498                    let network_id =
1499                        if self.generations_by_connection.default_network(&id).unwrap_or_default()
1500                            < self.current_generation.default_network
1501                        {
1502                            self.network_registry.default_network
1503                        } else {
1504                            None
1505                        };
1506                    if let Some(network_id) = network_id {
1507                        self.generations_by_connection
1508                            .set_default_network(id, self.current_generation);
1509                        let token = self
1510                            .tokens
1511                            .ensure_token(NetworkTokenContents { network_id, is_default: true })
1512                            .get()
1513                            .duplicate()
1514                            .context("could not duplicate token")?;
1515                        responder
1516                            .send(fnp_properties::NetworksWatchDefaultResponse::Network(token))?;
1517                    } else {
1518                        client.responder = Some(responder);
1519                    }
1520                }
1521            }
1522            fnp_properties::NetworksRequest::WatchProperties {
1523                payload:
1524                    fnp_properties::NetworksWatchPropertiesRequest {
1525                        network, properties, watcher, ..
1526                    },
1527                responder,
1528            } => match (network, properties, watcher) {
1529                (None, _, _) | (_, None, _) | (_, _, None) => {
1530                    responder.send(Err(fnp_properties::WatchError::MissingRequiredArgument))?
1531                }
1532                (Some(network), Some(properties), Some(watcher)) => {
1533                    if properties == fnp_properties::PropertyInterest::default() {
1534                        responder.send(Err(fnp_properties::WatchError::NoProperties))?
1535                    } else {
1536                        match self.tokens.get_contents(&network) {
1537                            Err(e) => {
1538                                warn!("Unknown network token. ({network:?}: {e})");
1539                                responder
1540                                    .send(Err(fnp_properties::WatchError::InvalidNetworkToken))?
1541                            }
1542                            Ok(_network_contents) => {
1543                                // Bind the stateful PropertyWatcher session: cache the token and
1544                                // requested properties, register the watcher's event stream, and
1545                                // reply immediately.
1546                                let watcher_stream = watcher.into_stream();
1547                                let watcher_id = self.next_watcher_id.allocate();
1548                                let registration = Registration {
1549                                    token: network,
1550                                    properties,
1551                                    responder: None,
1552                                    control_handle: watcher_stream.control_handle(),
1553                                };
1554                                let previous =
1555                                    self.property_watchers.insert(watcher_id, registration);
1556                                assert!(
1557                                    previous.is_none(),
1558                                    "property watcher {watcher_id:?} is already registered"
1559                                );
1560                                self.add_stream((watcher_id, watcher_stream));
1561                                responder.send(Ok(()))?;
1562                            }
1563                        }
1564                    }
1565                }
1566            },
1567            fnp_properties::NetworksRequest::_UnknownMethod { ordinal, .. } => {
1568                warn!("Received unexpected request {ordinal}")
1569            }
1570        }
1571
1572        Ok(())
1573    }
1574
1575    /// Handles a single item produced by a [`PrivilegedNetworkLookupRequestStream`].
1576    fn handle_privileged_network_lookup_request(
1577        &mut self,
1578        req: Result<fnp_properties::PrivilegedNetworkLookupRequest, fidl::Error>,
1579    ) -> Result<(), anyhow::Error> {
1580        let req = req.context("while handling PrivilegedNetworkLookup request")?;
1581        match req {
1582            fnp_properties::PrivilegedNetworkLookupRequest::Lookup { payload, responder } => {
1583                let result = match payload {
1584                    fnp_properties::LookupRequest::StarnixHandle(starnix_handle) => {
1585                        // Each delegated network registered by Starnix has a unique handle, so at
1586                        // most one entry in `networks` will match `starnix_handle`. The returned
1587                        // token is keyed by `network_id` rather than `starnix_handle`, so it
1588                        // remains active even if `starnix_handle` is updated later, until the
1589                        // network itself is removed.
1590                        match self.network_registry.networks.iter().find_map(
1591                            |(&network_id, props)| {
1592                                props
1593                                    .delegated
1594                                    .as_ref()
1595                                    .is_some_and(|d| d.starnix_handle == starnix_handle)
1596                                    .then_some(network_id)
1597                            },
1598                        ) {
1599                            Some(network_id) => Ok(self
1600                                .tokens
1601                                .ensure_token(NetworkTokenContents {
1602                                    network_id,
1603                                    is_default: false,
1604                                })
1605                                .get()
1606                                .duplicate()
1607                                .context("could not duplicate token")?),
1608                            None => Err(fnp_properties::LookupError::NotFound),
1609                        }
1610                    }
1611                    fnp_properties::LookupRequest::__SourceBreaking { unknown_ordinal } => {
1612                        warn!(
1613                            "Received unknown LookupRequest union variant in \
1614                            PrivilegedNetworkLookup.Lookup: {unknown_ordinal}"
1615                        );
1616                        Err(fnp_properties::LookupError::NotFound)
1617                    }
1618                };
1619                if let Err(e) = responder.send(result) {
1620                    warn!("Could not send PrivilegedNetworkLookup.Lookup response: {e}");
1621                }
1622            }
1623            fnp_properties::PrivilegedNetworkLookupRequest::_UnknownMethod { ordinal, .. } => {
1624                warn!(
1625                    "Received unexpected fuchsia.net.policy.properties/PrivilegedNetworkLookup \
1626                    request {ordinal}"
1627                );
1628            }
1629        }
1630        Ok(())
1631    }
1632
1633    /// Handles a single item produced by a client's [`PropertyWatcherRequestStream`].
1634    ///
1635    /// # Panics
1636    ///
1637    /// Panics if `id` does not correspond to a currently registered client. A client is removed
1638    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1639    /// item from the stream belongs to a live client.
1640    pub async fn handle_property_watcher_request(
1641        &mut self,
1642        id: PropertyWatcherConnectionId,
1643        req: Option<Result<fnp_properties::PropertyWatcherRequest, fidl::Error>>,
1644    ) -> Result<(), anyhow::Error> {
1645        let mut entry = assert_matches!(
1646            self.property_watchers.entry(id),
1647            Entry::Occupied(entry) => entry,
1648            "request for unknown property watcher {id:?}"
1649        );
1650
1651        let req = match req {
1652            Some(Ok(req)) => req,
1653            Some(Err(e)) => {
1654                // A clean disconnect yields the terminal item below rather than an error, so
1655                // this is always abnormal: a malformed request or a channel-level read failure.
1656                // No epitaph is sent because the channel may no longer be writable.
1657                error!("Property watcher {id:?} stream error: {e}");
1658                entry.get_mut().close(None);
1659                return Ok(());
1660            }
1661            // The client's stream has terminated, either because the client went away or
1662            // because of a `close` above. This is the only place registrations are removed.
1663            None => {
1664                let _: Registration = entry.remove();
1665                let _: Option<_> = self.generations_by_connection.remove_properties(&id);
1666                return Ok(());
1667            }
1668        };
1669        match req {
1670            fnp_properties::PropertyWatcherRequest::Watch { responder } => {
1671                let registration = entry.get_mut();
1672                if registration.responder.is_some() {
1673                    warn!(
1674                        "Only one call to \
1675                        fuchsia.net.policy.properties/PropertyWatcher.Watch may be \
1676                        active per connection"
1677                    );
1678                    // Close the connection; the terminal item performs the cleanup.
1679                    registration.close(Some(zx::Status::ALREADY_EXISTS));
1680                } else {
1681                    registration.responder = Some(responder);
1682                    match self.tokens.get_contents(&registration.token) {
1683                        Ok(network_contents) => {
1684                            // Determine whether a new update is available
1685                            // (last_sent_generation < current_generation)
1686                            let is_initial =
1687                                self.generations_by_connection.properties(&id).is_none();
1688                            if is_initial
1689                                || self
1690                                    .generations_by_connection
1691                                    .properties(&id)
1692                                    .unwrap_or_default()
1693                                    < self.current_generation.properties
1694                            {
1695                                let mut updates = fnp_properties::PropertyUpdate::default();
1696                                updates.add_socket_marks(
1697                                    &self.network_registry,
1698                                    &network_contents,
1699                                    registration.properties,
1700                                );
1701                                updates.add_dns(
1702                                    &self.network_registry,
1703                                    &network_contents,
1704                                    registration.properties,
1705                                );
1706                                let network_is_known = self
1707                                    .network_registry
1708                                    .networks
1709                                    .contains_key(&network_contents.network_id);
1710                                if (is_initial && network_is_known)
1711                                    || updates != fnp_properties::PropertyUpdate::default()
1712                                {
1713                                    self.generations_by_connection
1714                                        .set_properties(id, self.current_generation);
1715                                    if let Some(responder) = registration.responder.take() {
1716                                        responder.send(Ok(&updates))?;
1717                                    }
1718                                }
1719                            }
1720                        }
1721                        // `NOT_FOUND` means the network was removed, and its token dropped,
1722                        // before this `Watch` call. Any other error is unexpected.
1723                        Err(e) => {
1724                            if e != zx::Status::NOT_FOUND {
1725                                warn!("Unexpected error fetching token contents: {e}");
1726                            }
1727                            let _: Option<_> =
1728                                self.generations_by_connection.remove_properties(&id);
1729                            if let Some(responder) = registration.responder.take() {
1730                                if let Err(e) = responder
1731                                    .send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
1732                                {
1733                                    warn!("Could not send to responder: {e}");
1734                                }
1735                                registration.close(None);
1736                            }
1737                        }
1738                    }
1739                }
1740            }
1741        }
1742
1743        Ok(())
1744    }
1745
1746    /// Handles delegated network updates coming from Starnix.
1747    ///
1748    /// Resolves network events requested through the `NetworkRegistry` interface, applies the
1749    /// corresponding properties changes, and yields the computed DNS configuration targets for
1750    /// output updates.
1751    ///
1752    /// TODO(https://fxbug.dev/428712735): Stop returning DnsServer list once
1753    /// dns-resolver learns about DNS via NetworkProperties.
1754    ///
1755    /// # Panics
1756    ///
1757    /// Panics if there is no currently active delegated networks client. A client is removed
1758    /// only when its stream yields the terminal `None` item, and the stream is fused, so every
1759    /// item from the stream belongs to a live client.
1760    pub async fn handle_delegated_networks_update(
1761        &mut self,
1762        update: Option<Result<fnp_properties::NetworkRegistryRequest, fidl::Error>>,
1763    ) -> DelegatedNetworkUpdateResult {
1764        use fnp_properties::{
1765            NetworkRegistryAddError, NetworkRegistryRemoveError, NetworkRegistryRequest,
1766            NetworkRegistrySetDefaultError, NetworkRegistryUpdateError,
1767        };
1768
1769        let control_handle = assert_matches!(
1770            self.delegated_networks_client.as_ref(),
1771            Some(control_handle) => control_handle,
1772            "request with no active delegated networks client"
1773        );
1774
1775        let req = match update {
1776            Some(Ok(req)) => req,
1777            Some(Err(e)) => {
1778                // A clean disconnect yields the terminal item below rather than an error, so
1779                // this is always abnormal: a malformed request or a channel-level read failure.
1780                // No epitaph is sent because the channel may no longer be writable.
1781                error!("Delegated networks client stream error: {e}");
1782                control_handle.shutdown();
1783                return DelegatedNetworkUpdateResult { dns_servers: None };
1784            }
1785            // The client's stream has terminated, either because the client went away or
1786            // because of a `shutdown` above. This is the only place the client is removed.
1787            None => {
1788                let _: Option<_> = self.delegated_networks_client.take();
1789                return self.clear_delegated_networks().await;
1790            }
1791        };
1792
1793        let action_result = match req {
1794            NetworkRegistryRequest::SetDefault { network_id, responder } => {
1795                let set_default_result = match network_id {
1796                    fposix_socket::OptionalUint32::Value(interface_id) => {
1797                        match InterfaceId::try_from(interface_id) {
1798                            Ok(id) => {
1799                                let delegated_id = NetworkId::delegated(id);
1800                                if self.network_registry.networks.contains_key(&delegated_id) {
1801                                    self.update(NetworkRegistryUpdate::ChangeNetwork(
1802                                        delegated_id,
1803                                        NetworkUpdate::MakeDefault,
1804                                    ))
1805                                    .await;
1806                                    Ok(())
1807                                } else {
1808                                    Err(NetworkRegistrySetDefaultError::NotFound)
1809                                }
1810                            }
1811                            Err(_) => Err(NetworkRegistrySetDefaultError::NotFound),
1812                        }
1813                    }
1814                    fposix_socket::OptionalUint32::Unset(_) => {
1815                        self.update(NetworkRegistryUpdate::unset_default()).await;
1816                        Ok(())
1817                    }
1818                };
1819
1820                self.metrics.record_set_default(&set_default_result);
1821
1822                self.respond_to_delegated_network_update(
1823                    set_default_result,
1824                    |reply| responder.send(reply),
1825                    "failed to send SetDefault result",
1826                )
1827            }
1828            NetworkRegistryRequest::Add { network, responder } => {
1829                let add_result = match ValidatedNetwork::try_from(network).map_err(Into::into) {
1830                    Ok(valid) => {
1831                        if self.network_registry.networks.contains_key(&valid.network_id) {
1832                            Err(NetworkRegistryAddError::DuplicateNetworkId)
1833                        } else {
1834                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1835                                valid.network_id,
1836                                NetworkUpdate::Properties(NetworkPropertiesChange {
1837                                    added: true,
1838                                    delegated: Some(valid.delegated),
1839                                    dns_servers: Some(valid.dns_servers),
1840                                    connectivity_state: valid.connectivity,
1841                                    name: valid.name,
1842                                    network_type: valid.network_type,
1843                                }),
1844                            ))
1845                            .await;
1846                            Ok(())
1847                        }
1848                    }
1849                    Err(e) => Err(e),
1850                };
1851
1852                self.metrics.record_add(&add_result);
1853
1854                self.respond_to_delegated_network_update(
1855                    add_result,
1856                    |reply| responder.send(reply),
1857                    "failed to send Add result",
1858                )
1859            }
1860            NetworkRegistryRequest::Update { network, responder } => {
1861                let update_result = match ValidatedNetwork::try_from(network).map_err(Into::into) {
1862                    Ok(valid) => {
1863                        if !self.network_registry.networks.contains_key(&valid.network_id) {
1864                            Err(NetworkRegistryUpdateError::NotFound)
1865                        } else {
1866                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1867                                valid.network_id,
1868                                NetworkUpdate::Properties(NetworkPropertiesChange {
1869                                    added: false,
1870                                    delegated: Some(valid.delegated),
1871                                    dns_servers: Some(valid.dns_servers),
1872                                    connectivity_state: valid.connectivity,
1873                                    name: valid.name,
1874                                    network_type: valid.network_type,
1875                                }),
1876                            ))
1877                            .await;
1878                            Ok(())
1879                        }
1880                    }
1881                    Err(e) => Err(e),
1882                };
1883
1884                self.metrics.record_update(&update_result);
1885
1886                self.respond_to_delegated_network_update(
1887                    update_result,
1888                    |reply| responder.send(reply),
1889                    "failed to send Update result",
1890                )
1891            }
1892            NetworkRegistryRequest::Remove { network_id, responder } => {
1893                let remove_result = match InterfaceId::try_from(network_id) {
1894                    Ok(id) => {
1895                        let delegated_id = NetworkId::delegated(id);
1896                        if self.network_registry.starnix_default == Some(delegated_id) {
1897                            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
1898                        } else if !self.network_registry.networks.contains_key(&delegated_id) {
1899                            Err(NetworkRegistryRemoveError::NotFound)
1900                        } else {
1901                            self.update(NetworkRegistryUpdate::ChangeNetwork(
1902                                delegated_id,
1903                                NetworkUpdate::Remove,
1904                            ))
1905                            .await;
1906                            Ok(())
1907                        }
1908                    }
1909                    Err(_) => Err(NetworkRegistryRemoveError::NotFound),
1910                };
1911
1912                self.metrics.record_remove(&remove_result);
1913
1914                self.respond_to_delegated_network_update(
1915                    remove_result,
1916                    |reply| responder.send(reply),
1917                    "failed to send Remove result",
1918                )
1919            }
1920        };
1921
1922        action_result
1923    }
1924
1925    /// Unsets `starnix_default` and removes all `NetworkId::Delegated` networks when the
1926    /// `NetworkRegistry` client disconnects, returning the updated DNS server list if any
1927    /// delegated networks were removed.
1928    async fn clear_delegated_networks(&mut self) -> DelegatedNetworkUpdateResult {
1929        // Unset `starnix_default` first because `RegisteredNetworks::apply` refuses to remove a
1930        // network while it is still marked as `starnix_default`.
1931        if self.network_registry.starnix_default.is_some() {
1932            self.update(NetworkRegistryUpdate::unset_default()).await;
1933        }
1934
1935        let delegated_ids = self
1936            .network_registry
1937            .networks
1938            .keys()
1939            .filter(|id| match id {
1940                NetworkId::Delegated(_) => true,
1941                NetworkId::Fuchsia(_) => false,
1942            })
1943            .copied()
1944            .collect::<Vec<_>>();
1945
1946        for id in &delegated_ids {
1947            self.update(NetworkRegistryUpdate::ChangeNetwork(*id, NetworkUpdate::Remove)).await;
1948        }
1949
1950        DelegatedNetworkUpdateResult {
1951            dns_servers: (!delegated_ids.is_empty())
1952                .then(|| self.network_registry.consolidated_dns_servers()),
1953        }
1954    }
1955
1956    // Resolves the operation result, sends the success or failure status to
1957    // the FIDL responder, and returns the updated network registry settings.
1958    fn respond_to_delegated_network_update<E, F>(
1959        &self,
1960        operation_result: Result<(), E>,
1961        send_response: F,
1962        context_message: &'static str,
1963    ) -> DelegatedNetworkUpdateResult
1964    where
1965        F: FnOnce(Result<(), E>) -> Result<(), fidl::Error>,
1966    {
1967        // Return consolidated DNS servers if the operation was successful.
1968        let dns_servers = operation_result
1969            .as_ref()
1970            .ok()
1971            .map(|()| self.network_registry.consolidated_dns_servers());
1972
1973        // Send success or failure status to the FIDL responder.
1974        if let Err(e) = send_response(operation_result) {
1975            if !e.is_closed() {
1976                error!(
1977                    "Failed to send delegated network update result \
1978                for {context_message}: {e}"
1979                );
1980            }
1981        }
1982
1983        DelegatedNetworkUpdateResult { dns_servers }
1984    }
1985
1986    // Converts `NetworkDnsServers` to `Vec<DnsServer_>` for a given network.
1987    //
1988    // Note: We prioritize IPv4 servers over IPv6 servers. This is impactful
1989    // when sending DNS servers through NetworkProperties or to dns-resolver.
1990    fn extract_dns_servers(
1991        dns_servers: &Option<fnp_properties::NetworkDnsServers>,
1992        network_id: u64,
1993    ) -> Vec<fnet_name::DnsServer_> {
1994        let make_server = |address| fnet_name::DnsServer_ {
1995            address: Some(address),
1996            source: Some(fnet_name::DnsServerSource::SocketProxy(
1997                fnet_name::SocketProxyDnsServerSource {
1998                    source_interface: Some(network_id),
1999                    ..Default::default()
2000                },
2001            )),
2002            ..Default::default()
2003        };
2004
2005        dns_servers
2006            .as_ref()
2007            .map(|dns| {
2008                dns.v4
2009                    .as_ref()
2010                    .into_iter()
2011                    .flatten()
2012                    .map(|&address| {
2013                        make_server(fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
2014                            address,
2015                            port: DNS_PORT,
2016                        }))
2017                    })
2018                    .chain(dns.v6.as_ref().into_iter().flatten().map(|&address| {
2019                        make_server(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
2020                            address,
2021                            port: DNS_PORT,
2022                            zone_index: 0,
2023                        }))
2024                    }))
2025                    .collect()
2026            })
2027            .unwrap_or_default()
2028    }
2029
2030    pub(crate) async fn handle_network_token_resolver_request(
2031        &mut self,
2032        request: Result<fnp_properties::NetworkTokenResolverRequest, fidl::Error>,
2033    ) -> Result<(), anyhow::Error> {
2034        use fnp_properties::NetworkTokenResolverResolveTokenError as ResolveTokenError;
2035
2036        let request = request.context("while handling NetworkTokenResolver request")?;
2037        match request {
2038            fnp_properties::NetworkTokenResolverRequest::ResolveToken { token, responder } => {
2039                let maybe_contents = self.tokens.get_contents(&token).copied();
2040                match maybe_contents {
2041                    Err(e) => {
2042                        warn!("Unknown network token. ({token:?}: {e})");
2043                        responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
2044                    }
2045                    Ok(contents) => {
2046                        if contents.is_default {
2047                            // This is a default network token, we need to grab
2048                            // the non-default variant.
2049                            let query = NetworkTokenContents { is_default: false, ..contents };
2050                            if let Some(tok) = self.tokens.get_token(&query) {
2051                                responder.send(tok.duplicate().map_err(|e| {
2052                                    warn!("Encountered issue duplicating generated token. {e}");
2053                                    ResolveTokenError::InvalidNetworkToken
2054                                }))?;
2055                            } else {
2056                                warn!("Requested canonical version of unregistered network.");
2057                                responder.send(Err(ResolveTokenError::InvalidNetworkToken))?;
2058                            }
2059                        } else {
2060                            responder.send(Ok(token))?;
2061                        }
2062                    }
2063                }
2064            }
2065            fidl_fuchsia_net_policy_properties::NetworkTokenResolverRequest::_UnknownMethod {
2066                ordinal,
2067                control_handle,
2068                method_type,
2069                ..
2070            } => warn!(
2071                "Encountered unknown method call on NetworkTokenResolver: {ordinal} \
2072                {control_handle:?} {method_type:?}"
2073            ),
2074        }
2075
2076        Ok(())
2077    }
2078
2079    async fn changed_default_network(
2080        &mut self,
2081        previous_default_network: Option<NetworkId>,
2082        property_watchers: &mut HashMap<PropertyWatcherConnectionId, Registration>,
2083    ) {
2084        for (id, registration) in property_watchers.iter_mut() {
2085            if let Ok(contents) = self.tokens.get_contents(&registration.token) {
2086                if contents.is_default {
2087                    let _: Option<_> = self.generations_by_connection.remove_properties(id);
2088                    if let Some(responder) = registration.responder.take() {
2089                        if let Err(e) =
2090                            responder.send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
2091                        {
2092                            warn!("Could not send to responder: {e}");
2093                        }
2094                        registration.close(None);
2095                    }
2096                }
2097            }
2098        }
2099        self.tokens.drop_if(|&c| {
2100            c.is_default && previous_default_network.is_some_and(|i| i == c.network_id)
2101        });
2102    }
2103
2104    pub async fn update(&mut self, update: NetworkRegistryUpdate) {
2105        let RegistryUpdateResult { event, default_changed } = self.network_registry.apply(update);
2106
2107        self.update_network_inspect();
2108
2109        if let UpdateApplied::None = event {
2110            if default_changed.is_none() {
2111                // Return early if there were absolutely no changes and the default stayed the same.
2112                return;
2113            }
2114        }
2115
2116        if event != UpdateApplied::None {
2117            self.current_generation.properties += 1;
2118        }
2119
2120        let mut property_watchers = HashMap::new();
2121        std::mem::swap(&mut self.property_watchers, &mut property_watchers);
2122
2123        // Clean up or register tokens based on whether the network was added or removed.
2124        match event {
2125            UpdateApplied::NetworkChanged { network_id, added: true, .. } => {
2126                let _ = self
2127                    .tokens
2128                    .ensure_token(NetworkTokenContents { network_id, is_default: false });
2129            }
2130            UpdateApplied::NetworkRemoved(network_id) => {
2131                info!("Removing network {network_id}. Reporting NETWORK_GONE to watchers.");
2132                // Notify all property watchers bound to the removed network. The watcher is
2133                // kept in the `property_watchers` map so that idle clients
2134                // can receive NETWORK_GONE.
2135                for (id, registration) in property_watchers.iter_mut() {
2136                    if let Ok(network) = self.tokens.get_contents(&registration.token) {
2137                        if network.network_id == network_id {
2138                            let _: Option<_> = self.generations_by_connection.remove_properties(id);
2139                            if let Some(responder) = registration.responder.take() {
2140                                if let Err(e) = responder
2141                                    .send(Err(fnp_properties::PropertyWatcherError::NetworkGone))
2142                                {
2143                                    warn!("Could not send to responder: {e}");
2144                                }
2145                                registration.close(None);
2146                            }
2147                        }
2148                    }
2149                }
2150                self.tokens.drop_if(|c| !c.is_default && c.network_id == network_id);
2151            }
2152            UpdateApplied::NetworkChanged { added: false, .. }
2153            | UpdateApplied::DnsChanged
2154            | UpdateApplied::None => {}
2155        }
2156
2157        // Notify watchers of default network changes if one occurred.
2158        if let Some(DefaultChangedEvent { previous_default }) = default_changed {
2159            self.notify_default_network_changed(previous_default, &mut property_watchers).await;
2160            std::mem::swap(&mut self.property_watchers, &mut property_watchers);
2161            self.maybe_notify_watchers();
2162            return;
2163        }
2164
2165        if let UpdateApplied::NetworkChanged { network_id, .. } = event {
2166            if let Some(telemetry) = &self.telemetry {
2167                if let Some(props) = self.network_registry.networks.get(&network_id) {
2168                    telemetry.send(TelemetryEvent::NetworkChanged(NetworkEventMetadata {
2169                        id: network_id.get().get(),
2170                        name: props.name.clone(),
2171                        transport: props
2172                            .network_type
2173                            .unwrap_or(fnp_properties::NetworkType::Unknown),
2174                        is_fuchsia_provisioned: network_id.is_fuchsia(),
2175                        connectivity_state: props.connectivity_state,
2176                    }));
2177                }
2178            }
2179        }
2180
2181        for (id, mut registration) in property_watchers {
2182            let mut updates = fnp_properties::PropertyUpdate::default();
2183            match self.tokens.get_contents(&registration.token) {
2184                Ok(network) => match event {
2185                    UpdateApplied::NetworkChanged {
2186                        network_id,
2187                        changed_marks,
2188                        changed_dns,
2189                        ..
2190                    } => {
2191                        if network.network_id == network_id {
2192                            if changed_marks {
2193                                updates.add_socket_marks(
2194                                    &self.network_registry,
2195                                    &network,
2196                                    registration.properties,
2197                                );
2198                            }
2199                            if changed_dns {
2200                                updates.add_dns(
2201                                    &self.network_registry,
2202                                    &network,
2203                                    registration.properties,
2204                                );
2205                            }
2206                        }
2207                    }
2208                    UpdateApplied::DnsChanged => {
2209                        updates.add_dns(&self.network_registry, &network, registration.properties);
2210                    }
2211                    UpdateApplied::NetworkRemoved(_id) => {}
2212                    UpdateApplied::None => {}
2213                },
2214                Err(e) => {
2215                    debug!(
2216                        "Token {:?} not found for watcher {:?};
2217                    network was likely removed while idle ({})",
2218                        registration.token, id, e
2219                    );
2220                }
2221            }
2222
2223            // Update the client's generation state to keep them in sync with the global
2224            // properties generation.
2225            let has_updates = updates != fnp_properties::PropertyUpdate::default();
2226            if self.generations_by_connection.properties(&id).is_some() {
2227                match (has_updates, registration.responder.take()) {
2228                    (true, Some(responder)) => {
2229                        // Sync generation and send update.
2230                        self.generations_by_connection.set_properties(id, self.current_generation);
2231                        if let Err(e) = responder.send(Ok(&updates)) {
2232                            warn!("Failed to send watch updates: {}", e);
2233                        }
2234                    }
2235                    (false, maybe_responder) => {
2236                        // Sync generation to catch up and restore responder.
2237                        self.generations_by_connection.set_properties(id, self.current_generation);
2238                        registration.responder = maybe_responder;
2239                    }
2240                    (true, None) => {
2241                        // If a relevant change occurs while the client is idle, we leave the client
2242                        // on the old generation so they receive the update immediately upon the next
2243                        // `Watch()` call.
2244                    }
2245                }
2246            }
2247
2248            assert_matches!(
2249                self.property_watchers.insert(id, registration),
2250                None,
2251                "Re-inserted in an existing registration slot."
2252            );
2253        }
2254        self.maybe_notify_watchers();
2255    }
2256
2257    async fn notify_default_network_changed(
2258        &mut self,
2259        old_default: Option<NetworkId>,
2260        property_watchers: &mut HashMap<PropertyWatcherConnectionId, Registration>,
2261    ) {
2262        self.changed_default_network(old_default, property_watchers).await;
2263        let default_network = self.network_registry.default_network;
2264        if let Some(telemetry) = &self.telemetry {
2265            match default_network {
2266                Some(default_network) => {
2267                    if let Some(props) = self.network_registry.networks.get(&default_network) {
2268                        telemetry.send(TelemetryEvent::DefaultNetworkChanged(
2269                            NetworkEventMetadata {
2270                                id: default_network.get().get(),
2271                                name: props.name.clone(),
2272                                transport: props
2273                                    .network_type
2274                                    .unwrap_or(fnp_properties::NetworkType::Unknown),
2275                                is_fuchsia_provisioned: default_network.is_fuchsia(),
2276                                connectivity_state: props.connectivity_state,
2277                            },
2278                        ));
2279                    } else {
2280                        warn!("Could not fetch network data for default network.");
2281                    }
2282                }
2283                None => telemetry.send(TelemetryEvent::DefaultNetworkLost),
2284            }
2285        }
2286        self.current_generation.default_network += 1;
2287        // Answer every outstanding `WatchDefault`. Only the responders are taken; the clients
2288        // stay registered until their streams end.
2289        let responders = self
2290            .networks_clients
2291            .iter_mut()
2292            .filter_map(|(id, client)| client.responder.take().map(|responder| (*id, responder)));
2293        for (id, responder) in responders {
2294            self.generations_by_connection.set_default_network(id, self.current_generation);
2295            let response = match default_network {
2296                Some(network_id) => {
2297                    match self
2298                        .tokens
2299                        .ensure_token(NetworkTokenContents { network_id, is_default: true })
2300                        .get()
2301                        .duplicate()
2302                    {
2303                        Ok(token) => fnp_properties::NetworksWatchDefaultResponse::Network(token),
2304                        Err(e) => {
2305                            warn!("Could not duplicate token: {e}");
2306                            continue;
2307                        }
2308                    }
2309                }
2310                None => fnp_properties::NetworksWatchDefaultResponse::NoDefaultNetwork(
2311                    fnp_properties::Empty,
2312                ),
2313            };
2314            if let Err(e) = responder.send(response) {
2315                warn!("Could not send to responder: {e}");
2316            }
2317        }
2318    }
2319}
2320
2321#[cfg(test)]
2322mod tests {
2323    use super::*;
2324    use assert_matches::assert_matches;
2325    use diagnostics_assertions::assert_data_tree;
2326    use fnp_properties::{
2327        NetworkInfo, NetworkRegistryAddError, NetworkRegistryMarker, NetworkRegistryRemoveError,
2328        NetworkRegistrySetDefaultError, NetworkRegistryUpdateError, StarnixNetworkInfo,
2329    };
2330    use futures::FutureExt as _;
2331    use std::num::NonZeroU64;
2332    use test_case::test_case;
2333
2334    const ID_1: InterfaceId = InterfaceId(NonZeroU64::new(1).unwrap());
2335    const ID_2: InterfaceId = InterfaceId(NonZeroU64::new(2).unwrap());
2336    const NAME_1: &str = "testif1";
2337    const NAME_2: &str = "testif2";
2338
2339    const FUCHSIA_ID_1: NetworkId = NetworkId::Fuchsia(ID_1);
2340    const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
2341    const DELEGATED_ID_1: NetworkId = NetworkId::Delegated(ID_1);
2342    const DELEGATED_ID_2: NetworkId = NetworkId::Delegated(ID_2);
2343
2344    #[derive(Clone, Copy)]
2345    struct TestNetwork {
2346        id: NetworkId,
2347        name: &'static str,
2348        network_type: fnp_properties::NetworkType,
2349    }
2350
2351    #[derive(Default)]
2352    struct AppliedChanges {
2353        added: bool,
2354        changed_marks: bool,
2355        changed_dns: bool,
2356    }
2357
2358    impl TestNetwork {
2359        fn added(&self, added: bool) -> NetworkPropertiesChange {
2360            NetworkPropertiesChange {
2361                added,
2362                name: Some(self.name.to_string()),
2363                network_type: Some(self.network_type),
2364                ..Default::default()
2365            }
2366        }
2367
2368        fn change_with(
2369            &self,
2370            added: bool,
2371            marks: Option<fnet::Marks>,
2372            dns_servers: Option<Vec<fnet_name::DnsServer_>>,
2373            connectivity_state: Option<fnp_properties::ConnectivityState>,
2374        ) -> NetworkPropertiesChange {
2375            NetworkPropertiesChange {
2376                added,
2377                delegated: marks
2378                    .map(|marks| DelegatedNetworkProperties { marks, ..Default::default() }),
2379                dns_servers,
2380                connectivity_state,
2381                name: Some(self.name.to_string()),
2382                network_type: Some(self.network_type),
2383            }
2384        }
2385
2386        fn applied(&self, changes: AppliedChanges) -> UpdateApplied {
2387            let AppliedChanges { added, changed_marks, changed_dns } = changes;
2388            UpdateApplied::NetworkChanged {
2389                network_id: self.id,
2390                added,
2391                changed_marks,
2392                changed_dns,
2393                name: Some(self.name.to_string()),
2394                network_type: Some(self.network_type),
2395            }
2396        }
2397    }
2398
2399    const DELEGATED_NET_1: TestNetwork = TestNetwork {
2400        id: DELEGATED_ID_1,
2401        name: NAME_1,
2402        network_type: fnp_properties::NetworkType::Ethernet,
2403    };
2404    const FUCHSIA_NET_1: TestNetwork = TestNetwork {
2405        id: FUCHSIA_ID_1,
2406        name: NAME_1,
2407        network_type: fnp_properties::NetworkType::Ethernet,
2408    };
2409    const FUCHSIA_NET_2: TestNetwork = TestNetwork {
2410        id: FUCHSIA_ID_2,
2411        name: NAME_2,
2412        network_type: fnp_properties::NetworkType::Wifi,
2413    };
2414
2415    fn test_marks() -> fnet::Marks {
2416        fnet::Marks { mark_1: Some(123), ..Default::default() }
2417    }
2418
2419    fn test_marks_updated() -> fnet::Marks {
2420        fnet::Marks { mark_1: Some(456), ..Default::default() }
2421    }
2422
2423    fn test_dns_1() -> Vec<fnet_name::DnsServer_> {
2424        vec![fnet_name::DnsServer_ {
2425            address: Some(net_declare::fidl_socket_addr!("192.0.2.1:53")),
2426            ..Default::default()
2427        }]
2428    }
2429
2430    fn test_dns_2() -> Vec<fnet_name::DnsServer_> {
2431        vec![fnet_name::DnsServer_ {
2432            address: Some(net_declare::fidl_socket_addr!("192.0.2.2:53")),
2433            ..Default::default()
2434        }]
2435    }
2436
2437    fn delegated_properties() -> NetworkProperties {
2438        NetworkProperties {
2439            delegated: Some(DelegatedNetworkProperties {
2440                marks: test_marks(),
2441                ..Default::default()
2442            }),
2443            ..Default::default()
2444        }
2445    }
2446
2447    fn fuchsia_properties() -> NetworkProperties {
2448        NetworkProperties::default()
2449    }
2450
2451    fn added_properties(name: &str) -> NetworkPropertiesChange {
2452        NetworkPropertiesChange {
2453            added: true,
2454            delegated: None,
2455            dns_servers: None,
2456            connectivity_state: None,
2457            name: Some(name.to_string()),
2458            network_type: Some(fnp_properties::NetworkType::Ethernet),
2459        }
2460    }
2461
2462    impl NetpolNetworksService {
2463        pub(crate) fn default_network(&self) -> Option<NetworkId> {
2464            self.network_registry.default_network
2465        }
2466
2467        pub(crate) fn has_network(&self, id: NetworkId) -> bool {
2468            self.network_registry.networks.contains_key(&id)
2469        }
2470
2471        pub(crate) fn has_token(&self, network_id: NetworkId, is_default: bool) -> bool {
2472            self.tokens.get_token(&NetworkTokenContents { network_id, is_default }).is_some()
2473        }
2474
2475        pub(crate) fn ensure_token_for_test(&mut self, network_id: NetworkId, is_default: bool) {
2476            let _token: crate::network::token_registry::TokenEntry<'_, _> =
2477                self.tokens.ensure_token(NetworkTokenContents { network_id, is_default });
2478        }
2479    }
2480
2481    #[test_case(
2482        DELEGATED_NET_1,
2483        Some(test_marks()),
2484        Some(test_marks_updated()); "delegated network"
2485    )]
2486    #[test_case(FUCHSIA_NET_2, None, None; "fuchsia network")]
2487    fn test_handle_changed_network(
2488        net: TestNetwork,
2489        initial_marks: Option<fnet::Marks>,
2490        updated_marks: Option<fnet::Marks>,
2491    ) {
2492        let mut networks = RegisteredNetworks::default();
2493        let dns1 = test_dns_1();
2494        let dns2 = test_dns_2();
2495
2496        // Adding network with DNS servers should have changed_marks=true and changed_dns=true.
2497        let event = net.change_with(
2498            true,
2499            initial_marks.clone(),
2500            Some(dns1.clone()),
2501            Some(fnp_properties::ConnectivityState::FullConnectivity),
2502        );
2503        assert_eq!(
2504            networks.handle_changed_network(net.id, event),
2505            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
2506        );
2507        assert_eq!(
2508            networks.networks.get(&net.id).expect("network should be present"),
2509            &NetworkProperties {
2510                delegated: initial_marks
2511                    .clone()
2512                    .map(|marks| DelegatedNetworkProperties { marks, ..Default::default() }),
2513                dns_servers: dns1,
2514                connectivity_state: Some(fnp_properties::ConnectivityState::FullConnectivity),
2515                name: Some(net.name.to_string()),
2516                network_type: Some(net.network_type),
2517            }
2518        );
2519
2520        // Updating with same marks and different DNS should have changed_marks=false and
2521        // changed_dns=true.
2522        let event = net.change_with(
2523            false,
2524            initial_marks.clone(),
2525            Some(dns2.clone()),
2526            Some(fnp_properties::ConnectivityState::NoConnectivity),
2527        );
2528        assert_eq!(
2529            networks.handle_changed_network(net.id, event),
2530            net.applied(AppliedChanges { added: false, changed_marks: false, changed_dns: true }),
2531        );
2532        assert_eq!(
2533            networks.networks.get(&net.id).expect("network should be present"),
2534            &NetworkProperties {
2535                delegated: initial_marks
2536                    .clone()
2537                    .map(|marks| DelegatedNetworkProperties { marks, ..Default::default() }),
2538                dns_servers: dns2.clone(),
2539                connectivity_state: Some(fnp_properties::ConnectivityState::NoConnectivity),
2540                name: Some(net.name.to_string()),
2541                network_type: Some(net.network_type),
2542            }
2543        );
2544
2545        // Updating marks (if delegated) with same DNS should have changed_dns=false.
2546        let marks_changed = initial_marks != updated_marks;
2547        let event = net.change_with(
2548            false,
2549            updated_marks.clone(),
2550            Some(dns2.clone()),
2551            Some(fnp_properties::ConnectivityState::NoConnectivity),
2552        );
2553        assert_eq!(
2554            networks.handle_changed_network(net.id, event),
2555            net.applied(AppliedChanges {
2556                added: false,
2557                changed_marks: marks_changed,
2558                changed_dns: false,
2559            }),
2560        );
2561        assert_eq!(
2562            networks.networks.get(&net.id).expect("network should be present"),
2563            &NetworkProperties {
2564                delegated: updated_marks
2565                    .map(|marks| DelegatedNetworkProperties { marks, ..Default::default() }),
2566                dns_servers: dns2,
2567                connectivity_state: Some(fnp_properties::ConnectivityState::NoConnectivity),
2568                name: Some(net.name.to_string()),
2569                network_type: Some(net.network_type),
2570            }
2571        );
2572    }
2573
2574    #[test]
2575    fn test_handle_changed_network_validation() {
2576        let mut networks = RegisteredNetworks::default();
2577        let delegated =
2578            Some(DelegatedNetworkProperties { marks: test_marks(), ..Default::default() });
2579        let net = DELEGATED_NET_1;
2580
2581        // Update a non-added network
2582        let event = NetworkPropertiesChange { delegated: delegated.clone(), ..net.added(false) };
2583        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2584
2585        // Add the network
2586        let event = NetworkPropertiesChange { delegated: delegated.clone(), ..net.added(true) };
2587
2588        assert_eq!(
2589            networks.handle_changed_network(net.id, event),
2590            net.applied(AppliedChanges { added: true, changed_marks: true, changed_dns: true }),
2591        );
2592
2593        // Add already added network
2594        let event = NetworkPropertiesChange { delegated: delegated.clone(), ..net.added(true) };
2595        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2596
2597        // Fuchsia network with delegated properties
2598        let fuchsia_net = FUCHSIA_NET_1;
2599        let event = NetworkPropertiesChange { delegated, ..fuchsia_net.added(true) };
2600        assert_eq!(networks.handle_changed_network(fuchsia_net.id, event), UpdateApplied::None);
2601
2602        // Delegated network without delegated properties
2603        let event = net.added(true);
2604        assert_eq!(networks.handle_changed_network(net.id, event), UpdateApplied::None);
2605    }
2606
2607    // Unit tests the election algorithm directly by manipulating internal state.
2608    // Verifies prioritization and intermediate fallback election logic.
2609    #[test]
2610    fn fallback_election_and_prioritization() {
2611        let mut networks = RegisteredNetworks::default();
2612
2613        // Initial State: Empty, no default network.
2614        assert_eq!(networks.calculate_active_default(), None);
2615
2616        // Add a delegated network and set as the Starnix default.
2617        let _ = networks.networks.insert(DELEGATED_ID_1, delegated_properties());
2618        networks.starnix_default = Some(DELEGATED_ID_1);
2619        assert_eq!(
2620            networks.handle_default_network_update(),
2621            Some(DefaultChangedEvent { previous_default: None })
2622        );
2623        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2624
2625        // Replace the delegated network with another delegated network.
2626        // The new network should take over.
2627        let _ = networks.networks.insert(DELEGATED_ID_2, delegated_properties());
2628        networks.starnix_default = Some(DELEGATED_ID_2);
2629        assert_eq!(
2630            networks.handle_default_network_update(),
2631            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_1) })
2632        );
2633        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
2634
2635        // Add a Fuchsia network. This Fuchsia network should take over because of
2636        // Fuchsia network priority.
2637        let _ = networks.networks.insert(FUCHSIA_ID_2, fuchsia_properties());
2638        assert_eq!(
2639            networks.handle_default_network_update(),
2640            Some(DefaultChangedEvent { previous_default: Some(DELEGATED_ID_2) })
2641        );
2642        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2643
2644        // Add a Fuchsia network with a smaller ID. The smaller ID Fuchsia network
2645        // should take over.
2646        let _ = networks.networks.insert(FUCHSIA_ID_1, fuchsia_properties());
2647        assert_eq!(
2648            networks.handle_default_network_update(),
2649            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2650        );
2651        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2652
2653        // Remove FUCHSIA_ID_1. The next Fuchsia network should take over.
2654        let _ = networks.networks.remove(&FUCHSIA_ID_1);
2655        assert_eq!(
2656            networks.handle_default_network_update(),
2657            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
2658        );
2659        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2660
2661        // Remove FUCHSIA_ID_2. There are no more Fuchsia networks, so the default
2662        // should fallback to the delegated network.
2663        let _ = networks.networks.remove(&FUCHSIA_ID_2);
2664        assert_eq!(
2665            networks.handle_default_network_update(),
2666            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2667        );
2668        assert_eq!(networks.default_network, Some(DELEGATED_ID_2));
2669
2670        // Unset the default delegated network prior to removal.
2671        assert_eq!(
2672            networks.apply(NetworkRegistryUpdate::UnsetDefaultNetwork),
2673            RegistryUpdateResult {
2674                event: UpdateApplied::None,
2675                default_changed: Some(DefaultChangedEvent {
2676                    previous_default: Some(DELEGATED_ID_2)
2677                })
2678            }
2679        );
2680        assert_eq!(networks.default_network, None);
2681
2682        // Remove the delegated network.
2683        assert_eq!(
2684            networks
2685                .apply(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove)),
2686            RegistryUpdateResult {
2687                event: UpdateApplied::NetworkRemoved(DELEGATED_ID_1),
2688                default_changed: None
2689            }
2690        );
2691    }
2692
2693    // Tests the integration of `RegisteredNetworks::apply` updates, verifying
2694    // fallback priority from Fuchsia to Delegated networks and ensuring that
2695    // active default delegated networks cannot be removed.
2696    #[test]
2697    fn test_remove_fuchsia_network_fallback() {
2698        let mut networks = RegisteredNetworks::default();
2699        let marks = test_marks();
2700        let fuchsia_added = NetworkPropertiesChange { added: true, ..Default::default() };
2701
2702        // Add a Fuchsia network. This should become the default network.
2703        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2704            FUCHSIA_ID_1,
2705            NetworkUpdate::Properties(fuchsia_added.clone()),
2706        ));
2707        assert_matches!(
2708            result.event,
2709            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2710            if id == FUCHSIA_ID_1
2711        );
2712        assert_eq!(result.default_changed, Some(DefaultChangedEvent { previous_default: None }));
2713
2714        // Add a second Fuchsia network. This should not change the default network.
2715        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2716            FUCHSIA_ID_2,
2717            NetworkUpdate::Properties(fuchsia_added),
2718        ));
2719        assert_matches!(
2720            result.event,
2721            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2722            if id == FUCHSIA_ID_2
2723        );
2724        assert_eq!(result.default_changed, None);
2725
2726        // Add a delegated network. This should not change the default network.
2727        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2728            DELEGATED_ID_1,
2729            NetworkUpdate::Properties(NetworkPropertiesChange {
2730                added: true,
2731                delegated: Some(DelegatedNetworkProperties { marks, ..Default::default() }),
2732                ..Default::default()
2733            }),
2734        ));
2735        assert_matches!(
2736            result.event,
2737            UpdateApplied::NetworkChanged { network_id: id, added: true, .. }
2738            if id == DELEGATED_ID_1
2739        );
2740        assert_eq!(result.default_changed, None);
2741
2742        // Make the delegated network default (ignored because a Fuchsia
2743        // network is present).
2744        let result = networks.apply(NetworkRegistryUpdate::ChangeNetwork(
2745            DELEGATED_ID_1,
2746            NetworkUpdate::MakeDefault,
2747        ));
2748        assert_eq!(
2749            result,
2750            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
2751        );
2752        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2753
2754        // Remove the first Fuchsia network (fallback to the second Fuchsia
2755        // network).
2756        let result = networks
2757            .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove));
2758        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_1));
2759        assert_eq!(
2760            result.default_changed,
2761            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_1) })
2762        );
2763        assert_eq!(networks.default_network, Some(FUCHSIA_ID_2));
2764
2765        // Remove the second Fuchsia network (fallback to the
2766        // delegated network since it is default).
2767        let result = networks
2768            .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove));
2769        assert_eq!(result.event, UpdateApplied::NetworkRemoved(FUCHSIA_ID_2));
2770        assert_eq!(
2771            result.default_changed,
2772            Some(DefaultChangedEvent { previous_default: Some(FUCHSIA_ID_2) })
2773        );
2774        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2775
2776        // Remove the delegated network (rejected because it is default).
2777        let result = networks
2778            .apply(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove));
2779        assert_eq!(
2780            result,
2781            RegistryUpdateResult { event: UpdateApplied::None, default_changed: None }
2782        );
2783        assert!(networks.networks.contains_key(&DELEGATED_ID_1));
2784        assert_eq!(networks.default_network, Some(DELEGATED_ID_1));
2785    }
2786
2787    #[test]
2788    fn remove_non_default_fuchsia_preserves_default() {
2789        let mut networks = RegisteredNetworks::default();
2790
2791        // Add both networks.
2792        let _ = networks.networks.insert(FUCHSIA_ID_1, NetworkProperties::default());
2793        let _ = networks.networks.insert(FUCHSIA_ID_2, NetworkProperties::default());
2794
2795        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
2796        assert_eq!(
2797            networks.handle_default_network_update(),
2798            Some(DefaultChangedEvent { previous_default: None })
2799        );
2800        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2801
2802        // Remove the non-default network.
2803        assert_eq!(
2804            networks
2805                .apply(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove)),
2806            RegistryUpdateResult {
2807                event: UpdateApplied::NetworkRemoved(FUCHSIA_ID_2),
2808                default_changed: None
2809            }
2810        );
2811
2812        // Verify that FUCHSIA_ID_1 is still the active default.
2813        assert_eq!(networks.default_network, Some(FUCHSIA_ID_1));
2814    }
2815
2816    #[fuchsia::test]
2817    async fn remove_default_network_cleans_up_tokens() {
2818        let mut service = NetpolNetworksService::default();
2819
2820        // Add two Fuchsia networks via ChangeNetwork updates.
2821        service
2822            .update(NetworkRegistryUpdate::ChangeNetwork(
2823                FUCHSIA_ID_1,
2824                NetworkUpdate::Properties(added_properties(NAME_1)),
2825            ))
2826            .await;
2827
2828        service
2829            .update(NetworkRegistryUpdate::ChangeNetwork(
2830                FUCHSIA_ID_2,
2831                NetworkUpdate::Properties(added_properties(NAME_2)),
2832            ))
2833            .await;
2834
2835        // On election, FUCHSIA_ID_1 (the smaller ID) is elected as default.
2836        assert_eq!(service.default_network(), Some(FUCHSIA_ID_1));
2837
2838        // Ensure non-default tokens exist for both networks.
2839        assert!(service.has_token(FUCHSIA_ID_1, false /* is_default */));
2840        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
2841
2842        // Manually create default token for FUCHSIA_ID_1 to simulate a client WatchDefault call.
2843        service.ensure_token_for_test(FUCHSIA_ID_1, true /* is_default */);
2844        assert!(service.has_token(FUCHSIA_ID_1, true /* is_default */));
2845
2846        // Remove FUCHSIA_ID_1 (the default network). This should trigger fallback to FUCHSIA_ID_2
2847        // and clean up FUCHSIA_ID_1's tokens.
2848        service
2849            .update(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_1, NetworkUpdate::Remove))
2850            .await;
2851
2852        // Verify fallback happened.
2853        assert_eq!(service.default_network(), Some(FUCHSIA_ID_2));
2854
2855        // Verify FUCHSIA_ID_1 tokens are gone.
2856        assert!(!service.has_token(FUCHSIA_ID_1, false /* is_default */));
2857        assert!(!service.has_token(FUCHSIA_ID_1, true /* is_default */));
2858
2859        // Verify FUCHSIA_ID_2 tokens still exist.
2860        assert!(service.has_token(FUCHSIA_ID_2, false /* is_default */));
2861    }
2862
2863    #[fuchsia::test]
2864    async fn test_property_watcher_generation_increment_on_unpolled_update() {
2865        let mut service = NetpolNetworksService::default();
2866
2867        // Set up initial network state and register a PropertyWatcher connection.
2868        service
2869            .update(NetworkRegistryUpdate::ChangeNetwork(
2870                DELEGATED_ID_1,
2871                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2872                    true,
2873                    Some(test_marks()),
2874                    None,
2875                    None,
2876                )),
2877            ))
2878            .await;
2879
2880        let token = service
2881            .tokens
2882            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2883            .get()
2884            .duplicate()
2885            .unwrap();
2886
2887        let (watcher, server_end) =
2888            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2889        let (networks_proxy, networks_stream) =
2890            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2891
2892        service.add_stream(networks_stream);
2893
2894        // Watch for socket marks changes on `DELEGATED_ID_1` and expect the initial state.
2895        let request = fnp_properties::NetworksWatchPropertiesRequest {
2896            network: Some(token),
2897            properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2898            watcher: Some(server_end),
2899            ..Default::default()
2900        };
2901
2902        let watch_req = networks_proxy.watch_properties(request);
2903        let req_event = service.select_next_some().await;
2904        assert_eq!(
2905            service.handle_event(req_event).await.expect("Failed to handle event"),
2906            DelegatedNetworkUpdateResult::default()
2907        );
2908        assert_matches!(watch_req.await, Ok(Ok(())));
2909
2910        // First watch call should return the initial state.
2911        let watch_fut1 = watcher.watch();
2912        let pw_event1 = service.select_next_some().await;
2913        assert_eq!(
2914            service.handle_event(pw_event1).await.expect("Failed to handle event"),
2915            DelegatedNetworkUpdateResult::default()
2916        );
2917        let initial_updates = watch_fut1.await.unwrap().unwrap();
2918        assert_eq!(
2919            initial_updates,
2920            fnp_properties::PropertyUpdate {
2921                socket_marks: Some(test_marks()),
2922                dns_configuration: None,
2923                ..Default::default()
2924            }
2925        );
2926
2927        // Update the network properties while the client is idle, ensuring the server increments
2928        // its generation without bumping the unpolled client's generation.
2929        service
2930            .update(NetworkRegistryUpdate::ChangeNetwork(
2931                DELEGATED_ID_1,
2932                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
2933                    false,
2934                    Some(test_marks_updated()),
2935                    None,
2936                    None,
2937                )),
2938            ))
2939            .await;
2940
2941        // Verify the pending change is immediately available to the client.
2942        let watch_fut2 = watcher.watch();
2943        let pw_event2 = service.select_next_some().await;
2944        assert_eq!(
2945            service.handle_event(pw_event2).await.expect("Failed to handle event"),
2946            DelegatedNetworkUpdateResult::default()
2947        );
2948        let updates = watch_fut2.await.unwrap().unwrap();
2949        assert_eq!(
2950            updates,
2951            fnp_properties::PropertyUpdate {
2952                socket_marks: Some(test_marks_updated()),
2953                dns_configuration: None,
2954                ..Default::default()
2955            }
2956        );
2957    }
2958
2959    #[fuchsia::test]
2960    async fn test_watch_should_return_error_on_concurrent_call() {
2961        let mut service = NetpolNetworksService::default();
2962
2963        let token = service
2964            .tokens
2965            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
2966            .get()
2967            .duplicate()
2968            .unwrap();
2969
2970        let (watcher, server_end) =
2971            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
2972        let (networks_proxy, networks_stream) =
2973            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
2974
2975        service.add_stream(networks_stream);
2976
2977        let watch_req =
2978            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
2979                network: Some(token),
2980                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
2981                watcher: Some(server_end),
2982                ..Default::default()
2983            });
2984        let req_event = service.select_next_some().await;
2985        assert_eq!(
2986            service.handle_event(req_event).await.expect("Failed to handle event"),
2987            DelegatedNetworkUpdateResult::default()
2988        );
2989        assert_matches!(watch_req.await, Ok(Ok(())));
2990
2991        // Call `watch()` twice concurrently on the same connection.
2992        let watch_fut1 = watcher.watch();
2993        let pw_event1 = service.select_next_some().await;
2994        assert_eq!(
2995            service.handle_event(pw_event1).await.expect("Failed to handle event"),
2996            DelegatedNetworkUpdateResult::default()
2997        );
2998
2999        let watch_fut2 = watcher.watch();
3000        let pw_event2 = service.select_next_some().await;
3001        assert_eq!(
3002            service.handle_event(pw_event2).await.expect("Failed to handle event"),
3003            DelegatedNetworkUpdateResult::default()
3004        );
3005
3006        for res in [watch_fut1.await, watch_fut2.await] {
3007            assert_matches!(
3008                res,
3009                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
3010                    if epitaph == zx::Status::ALREADY_EXISTS
3011            );
3012        }
3013        // The registration is removed when the closed stream yields its terminal item.
3014        let disconnect_event = service.select_next_some().await;
3015        assert_eq!(
3016            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3017            DelegatedNetworkUpdateResult::default()
3018        );
3019        assert!(service.property_watchers.is_empty());
3020    }
3021
3022    #[fuchsia::test]
3023    async fn test_watch_default_should_return_error_on_concurrent_call() {
3024        let mut service = NetpolNetworksService::default();
3025
3026        let (networks_proxy, networks_stream) =
3027            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3028
3029        service.add_stream(networks_stream);
3030
3031        // Call `watch_default` twice concurrently on the same connection.
3032        let watch_fut1 = networks_proxy.watch_default();
3033        let req_event1 = service.select_next_some().await;
3034        assert_eq!(
3035            service.handle_event(req_event1).await.expect("Failed to handle event"),
3036            DelegatedNetworkUpdateResult::default()
3037        );
3038
3039        let watch_fut2 = networks_proxy.watch_default();
3040        let req_event2 = service.select_next_some().await;
3041        assert_eq!(
3042            service.handle_event(req_event2).await.expect("Failed to handle event"),
3043            DelegatedNetworkUpdateResult::default()
3044        );
3045
3046        for res in [watch_fut1.await, watch_fut2.await] {
3047            assert_matches!(
3048                res,
3049                Err(fidl::Error::ClientChannelClosed { epitaph, .. })
3050                    if epitaph == zx::Status::ALREADY_EXISTS
3051            );
3052        }
3053        // The client is removed when the closed stream yields its terminal item.
3054        let disconnect_event = service.select_next_some().await;
3055        assert_eq!(
3056            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3057            DelegatedNetworkUpdateResult::default()
3058        );
3059        assert!(service.networks_clients.is_empty());
3060    }
3061
3062    #[fuchsia::test]
3063    async fn test_network_removal_reports_network_gone() {
3064        let mut service = NetpolNetworksService::default();
3065
3066        service
3067            .update(NetworkRegistryUpdate::ChangeNetwork(
3068                DELEGATED_ID_1,
3069                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3070                    true,
3071                    Some(test_marks()),
3072                    None,
3073                    None,
3074                )),
3075            ))
3076            .await;
3077
3078        let token1 = service
3079            .tokens
3080            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
3081            .get()
3082            .duplicate()
3083            .unwrap();
3084        let token2 = service
3085            .tokens
3086            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
3087            .get()
3088            .duplicate()
3089            .unwrap();
3090
3091        let (active_watcher, active_server_end) =
3092            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
3093        let (idle_watcher, idle_server_end) =
3094            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
3095        let (networks_proxy, networks_stream) =
3096            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3097
3098        service.add_stream(networks_stream);
3099
3100        for (token, server_end) in [(token1, active_server_end), (token2, idle_server_end)] {
3101            let watch_req =
3102                networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
3103                    network: Some(token),
3104                    properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
3105                    watcher: Some(server_end),
3106                    ..Default::default()
3107                });
3108            let req_event = service.select_next_some().await;
3109            assert_eq!(
3110                service.handle_event(req_event).await.expect("Failed to handle event"),
3111                DelegatedNetworkUpdateResult::default()
3112            );
3113            assert_matches!(watch_req.await, Ok(Ok(())));
3114        }
3115
3116        // Active watcher's first watch call returns the initial snapshot.
3117        let active_watch_fut1 = active_watcher.watch();
3118        let active_pw_event1 = service.select_next_some().await;
3119        assert_eq!(
3120            service.handle_event(active_pw_event1).await.expect("Failed to handle event"),
3121            DelegatedNetworkUpdateResult::default()
3122        );
3123        let initial_updates = active_watch_fut1.await.unwrap().unwrap();
3124        assert_eq!(
3125            initial_updates,
3126            fnp_properties::PropertyUpdate {
3127                socket_marks: Some(test_marks()),
3128                dns_configuration: None,
3129                ..Default::default()
3130            }
3131        );
3132
3133        // Active watcher starts an in-flight watch call before network removal.
3134        let mut active_watch_fut2 = active_watcher.watch();
3135        let active_pw_event2 = service.select_next_some().await;
3136        assert_eq!(
3137            service.handle_event(active_pw_event2).await.expect("Failed to handle event"),
3138            DelegatedNetworkUpdateResult::default()
3139        );
3140        assert_matches!((&mut active_watch_fut2).now_or_never(), None);
3141
3142        // Remove the network.
3143        service
3144            .update(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove))
3145            .await;
3146
3147        // Active watcher immediately observes NetworkGone on its in-flight call, and its
3148        // stream yields the terminal item because `update` closed the channel.
3149        assert_matches!(
3150            active_watch_fut2.await,
3151            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
3152        );
3153        let active_closed_event = service.select_next_some().await;
3154        assert_eq!(
3155            service.handle_event(active_closed_event).await.expect("Failed to handle event"),
3156            DelegatedNetworkUpdateResult::default()
3157        );
3158
3159        // Idle watcher calls watch() after network removal and also observes NetworkGone.
3160        let idle_watch_fut = idle_watcher.watch();
3161        let idle_pw_event = service.select_next_some().await;
3162        assert_eq!(
3163            service.handle_event(idle_pw_event).await.expect("Failed to handle event"),
3164            DelegatedNetworkUpdateResult::default()
3165        );
3166        assert_matches!(
3167            idle_watch_fut.await,
3168            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
3169        );
3170    }
3171
3172    #[fuchsia::test]
3173    async fn test_idle_default_watcher_reports_network_gone_on_default_change() {
3174        let mut service = NetpolNetworksService::default();
3175
3176        // Add network and set it as the default network.
3177        service
3178            .update(NetworkRegistryUpdate::ChangeNetwork(
3179                DELEGATED_ID_1,
3180                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3181                    true,
3182                    Some(test_marks()),
3183                    None,
3184                    None,
3185                )),
3186            ))
3187            .await;
3188        service
3189            .update(NetworkRegistryUpdate::ChangeNetwork(
3190                DELEGATED_ID_1,
3191                NetworkUpdate::MakeDefault,
3192            ))
3193            .await;
3194
3195        let (networks_proxy, networks_stream) =
3196            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3197        service.add_stream(networks_stream);
3198
3199        // Client calls watch_default() to get default token.
3200        let default_fut = networks_proxy.watch_default();
3201        let req_event = service.select_next_some().await;
3202        assert_eq!(
3203            service.handle_event(req_event).await.expect("Failed to handle event"),
3204            DelegatedNetworkUpdateResult::default()
3205        );
3206        let default_token = match default_fut.await.unwrap() {
3207            fnp_properties::NetworksWatchDefaultResponse::Network(token) => token,
3208            res => panic!("Expected Network token, got {res:?}"),
3209        };
3210
3211        // Client registers PropertyWatcher for the default token.
3212        let (watcher, server_end) =
3213            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
3214        let watch_req =
3215            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
3216                network: Some(default_token),
3217                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
3218                watcher: Some(server_end),
3219                ..Default::default()
3220            });
3221        let req_event = service.select_next_some().await;
3222        assert_eq!(
3223            service.handle_event(req_event).await.expect("Failed to handle event"),
3224            DelegatedNetworkUpdateResult::default()
3225        );
3226        assert_matches!(watch_req.await, Ok(Ok(())));
3227
3228        // Client fetches initial property snapshot.
3229        let initial_fut = watcher.watch();
3230        let pw_event = service.select_next_some().await;
3231        assert_eq!(
3232            service.handle_event(pw_event).await.expect("Failed to handle event"),
3233            DelegatedNetworkUpdateResult::default()
3234        );
3235        assert_matches!(initial_fut.await, Ok(Ok(_)));
3236
3237        // Client has no pending watch calls. Unset the default network.
3238        service.update(NetworkRegistryUpdate::unset_default()).await;
3239
3240        // Idle watcher calls watch() after default network change and receives NetworkGone.
3241        let idle_watch_fut = watcher.watch();
3242        let idle_pw_event = service.select_next_some().await;
3243        assert_eq!(
3244            service.handle_event(idle_pw_event).await.expect("Failed to handle event"),
3245            DelegatedNetworkUpdateResult::default()
3246        );
3247        assert_matches!(
3248            idle_watch_fut.await,
3249            Ok(Err(fnp_properties::PropertyWatcherError::NetworkGone))
3250        );
3251    }
3252
3253    #[test_case(
3254        None,
3255        Some(fnp_properties::NetworkInfo::Starnix(
3256            fnp_properties::StarnixNetworkInfo {
3257                mark: Some(123),
3258                handle: Some(1),
3259                ..Default::default()
3260            }
3261        )),
3262        Err(NetworkValidationError::MissingNetworkId);
3263        "missing network_id"
3264    )]
3265    #[test_case(
3266        Some(0),
3267        Some(fnp_properties::NetworkInfo::Starnix(
3268            fnp_properties::StarnixNetworkInfo {
3269                mark: Some(123),
3270                handle: Some(1),
3271                ..Default::default()
3272            }
3273        )),
3274        Err(NetworkValidationError::MissingNetworkId);
3275        "zero network_id"
3276    )]
3277    #[test_case(
3278        Some(1),
3279        None,
3280        Err(NetworkValidationError::MissingNetworkInfo);
3281        "missing network_info"
3282    )]
3283    #[test_case(
3284        Some(1),
3285        Some(fnp_properties::NetworkInfo::Fuchsia(Default::default())),
3286        Err(NetworkValidationError::MissingNetworkInfo);
3287        "fuchsia network_info"
3288    )]
3289    #[test_case(
3290        Some(1),
3291        Some(fnp_properties::NetworkInfo::Starnix(
3292            fnp_properties::StarnixNetworkInfo {
3293                mark: Some(123),
3294                handle: None,
3295                ..Default::default()
3296            }
3297        )),
3298        Err(NetworkValidationError::MissingNetworkInfo);
3299        "missing starnix handle"
3300    )]
3301    #[test_case(
3302        Some(1),
3303        Some(fnp_properties::NetworkInfo::Starnix(
3304            fnp_properties::StarnixNetworkInfo {
3305                mark: Some(123),
3306                handle: Some(1),
3307                ..Default::default()
3308            }
3309        )),
3310        Ok(NetworkId::delegated(InterfaceId::new(1).unwrap()));
3311        "valid starnix network"
3312    )]
3313    fn test_validated_network_validation(
3314        network_id: Option<u32>,
3315        info: Option<fnp_properties::NetworkInfo>,
3316        expected: Result<NetworkId, NetworkValidationError>,
3317    ) {
3318        let net = fnp_properties::Network { network_id, info, ..Default::default() };
3319        let result = ValidatedNetwork::try_from(net).map(|v| v.network_id);
3320        assert_eq!(result, expected);
3321    }
3322
3323    fn starnix_network_payload(id: u32, mark: u32) -> fnp_properties::Network {
3324        fnp_properties::Network {
3325            network_id: Some(id),
3326            info: Some(NetworkInfo::Starnix(StarnixNetworkInfo {
3327                mark: Some(mark),
3328                handle: Some(id.into()),
3329                ..Default::default()
3330            })),
3331            ..Default::default()
3332        }
3333    }
3334
3335    async fn process_fidl_request<E>(
3336        service: &mut NetpolNetworksService,
3337        request_future: impl Future<Output = Result<Result<(), E>, fidl::Error>>,
3338    ) -> Result<(), E> {
3339        let event = service.select_next_some().await;
3340        let update_result = service.handle_event(event).await.expect("failed to handle event");
3341        let client_result = request_future.await.expect("FIDL call failed");
3342        assert_eq!(update_result.dns_servers.is_some(), client_result.is_ok());
3343        client_result
3344    }
3345
3346    #[fuchsia::test]
3347    async fn test_delegated_networks_fidl_errors() {
3348        let mut service = NetpolNetworksService::default();
3349        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3350        service.add_stream(stream);
3351
3352        const NETWORK_ID_1: u32 = 1;
3353        const NETWORK_ID_2: u32 = 2;
3354        const SOCKET_MARK: u32 = 100;
3355
3356        // Updating a network before it has been added returns NotFound.
3357        assert_eq!(
3358            process_fidl_request(
3359                &mut service,
3360                proxy.update(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK,)),
3361            )
3362            .await,
3363            Err(NetworkRegistryUpdateError::NotFound)
3364        );
3365
3366        // Adding a valid network succeeds.
3367        assert_eq!(
3368            process_fidl_request(
3369                &mut service,
3370                proxy.add(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK)),
3371            )
3372            .await,
3373            Ok(())
3374        );
3375
3376        // Adding a duplicate network ID returns DuplicateNetworkId.
3377        assert_eq!(
3378            process_fidl_request(
3379                &mut service,
3380                proxy.add(&starnix_network_payload(NETWORK_ID_1, SOCKET_MARK)),
3381            )
3382            .await,
3383            Err(NetworkRegistryAddError::DuplicateNetworkId)
3384        );
3385
3386        // Removing a non-existent network returns NotFound.
3387        assert_eq!(
3388            process_fidl_request(&mut service, proxy.remove(NETWORK_ID_2)).await,
3389            Err(NetworkRegistryRemoveError::NotFound)
3390        );
3391
3392        // Setting a non-existent network as default returns NotFound.
3393        assert_eq!(
3394            process_fidl_request(
3395                &mut service,
3396                proxy.set_default(&fposix_socket::OptionalUint32::Value(NETWORK_ID_2,)),
3397            )
3398            .await,
3399            Err(NetworkRegistrySetDefaultError::NotFound)
3400        );
3401
3402        // Setting an existing network as default succeeds.
3403        assert_eq!(
3404            process_fidl_request(
3405                &mut service,
3406                proxy.set_default(&fposix_socket::OptionalUint32::Value(NETWORK_ID_1,)),
3407            )
3408            .await,
3409            Ok(())
3410        );
3411
3412        // Attempting to remove the active default network returns
3413        // CannotRemoveDefaultNetwork.
3414        assert_eq!(
3415            process_fidl_request(&mut service, proxy.remove(NETWORK_ID_1)).await,
3416            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
3417        );
3418
3419        // Unsetting the default network succeeds.
3420        assert_eq!(
3421            process_fidl_request(
3422                &mut service,
3423                proxy.set_default(&fposix_socket::OptionalUint32::Unset(fposix_socket::Empty)),
3424            )
3425            .await,
3426            Ok(())
3427        );
3428
3429        // Once unset from default, removing the network succeeds.
3430        assert_eq!(process_fidl_request(&mut service, proxy.remove(NETWORK_ID_1)).await, Ok(()));
3431    }
3432
3433    #[fuchsia::test]
3434    async fn test_inspect_metrics() {
3435        let inspector = fuchsia_inspect::Inspector::default();
3436        let telemetry_node = inspector.root().create_child("telemetry");
3437        let mut service = NetpolNetworksService::default()
3438            .with_inspect(&telemetry_node, "operations", &telemetry_node, "network_registry")
3439            .expect("failed to initialize inspect");
3440        inspector.root().record(telemetry_node);
3441
3442        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
3443        service.add_stream(stream);
3444
3445        let make_network = |id: u32, mark: u32| fnp_properties::Network {
3446            name: Some("wlan0".to_string()),
3447            network_type: Some(fnp_properties::NetworkType::Wifi),
3448            connectivity: Some(fnp_properties::ConnectivityState::FullConnectivity),
3449            ..starnix_network_payload(id, mark)
3450        };
3451
3452        // Initial check - all values should be default.
3453        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3454        assert_data_tree!(
3455            hierarchy,
3456            root: contains {
3457                telemetry: contains {
3458                    operations: contains {
3459                        delegated: contains {
3460                            adds: contains { successes: 0u64, errors: 0u64 },
3461                            removes: contains { successes: 0u64, errors: 0u64 },
3462                            set_defaults: contains {
3463                                successes: 0u64,
3464                                errors: 0u64,
3465                            },
3466                            updates: contains {
3467                                successes: 0u64,
3468                                errors: 0u64,
3469                            },
3470                        }
3471                    }
3472                }
3473            }
3474        );
3475
3476        // Add valid network 1.
3477        assert_eq!(
3478            process_fidl_request(&mut service, proxy.add(&make_network(1, 100))).await,
3479            Ok(())
3480        );
3481
3482        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3483        assert_data_tree!(
3484            hierarchy,
3485            root: contains {
3486                telemetry: contains {
3487                    operations: contains {
3488                        delegated: contains {
3489                            adds: contains { successes: 1u64, errors: 0u64 },
3490                        }
3491                    }
3492                }
3493            }
3494        );
3495
3496        // Add duplicate network 1 -> error.
3497        assert_eq!(
3498            process_fidl_request(&mut service, proxy.add(&make_network(1, 100))).await,
3499            Err(NetworkRegistryAddError::DuplicateNetworkId)
3500        );
3501
3502        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3503        assert_data_tree!(
3504            hierarchy,
3505            root: contains {
3506                telemetry: contains {
3507                    operations: contains {
3508                        delegated: contains {
3509                            adds: contains { successes: 1u64, errors: 1u64 },
3510                        }
3511                    }
3512                }
3513            }
3514        );
3515
3516        // Update network 1 properties.
3517        assert_eq!(
3518            process_fidl_request(&mut service, proxy.update(&make_network(1, 150))).await,
3519            Ok(())
3520        );
3521
3522        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3523        assert_data_tree!(
3524            hierarchy,
3525            root: contains {
3526                telemetry: contains {
3527                    operations: contains {
3528                        delegated: contains {
3529                            updates: contains {
3530                                successes: 1u64,
3531                                errors: 0u64,
3532                            },
3533                        }
3534                    }
3535                }
3536            }
3537        );
3538
3539        // Make network 1 default.
3540        assert_eq!(
3541            process_fidl_request(
3542                &mut service,
3543                proxy.set_default(&fposix_socket::OptionalUint32::Value(1)),
3544            )
3545            .await,
3546            Ok(())
3547        );
3548
3549        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3550        assert_data_tree!(
3551            hierarchy,
3552            root: contains {
3553                telemetry: contains {
3554                    operations: contains {
3555                        delegated: contains {
3556                            set_defaults: contains {
3557                                successes: 1u64,
3558                                errors: 0u64,
3559                            },
3560                        }
3561                    }
3562                }
3563            }
3564        );
3565
3566        // Make non-existent network 2 default -> error.
3567        assert_eq!(
3568            process_fidl_request(
3569                &mut service,
3570                proxy.set_default(&fposix_socket::OptionalUint32::Value(2)),
3571            )
3572            .await,
3573            Err(NetworkRegistrySetDefaultError::NotFound)
3574        );
3575
3576        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3577        assert_data_tree!(
3578            hierarchy,
3579            root: contains {
3580                telemetry: contains {
3581                    operations: contains {
3582                        delegated: contains {
3583                            set_defaults: contains {
3584                                successes: 1u64,
3585                                errors: 1u64,
3586                            },
3587                        }
3588                    }
3589                }
3590            }
3591        );
3592
3593        // Try removing default network 1 -> error.
3594        assert_eq!(
3595            process_fidl_request(&mut service, proxy.remove(1)).await,
3596            Err(NetworkRegistryRemoveError::CannotRemoveDefaultNetwork)
3597        );
3598
3599        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3600        assert_data_tree!(
3601            hierarchy,
3602            root: contains {
3603                telemetry: contains {
3604                    operations: contains {
3605                        delegated: contains {
3606                            removes: contains { successes: 0u64, errors: 1u64 },
3607                        }
3608                    }
3609                }
3610            }
3611        );
3612
3613        // Unset default.
3614        assert_eq!(
3615            process_fidl_request(
3616                &mut service,
3617                proxy.set_default(&fposix_socket::OptionalUint32::Unset(fposix_socket::Empty)),
3618            )
3619            .await,
3620            Ok(())
3621        );
3622
3623        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3624        assert_data_tree!(
3625            hierarchy,
3626            root: contains {
3627                telemetry: contains {
3628                    operations: contains {
3629                        delegated: contains {
3630                            set_defaults: contains {
3631                                successes: 2u64,
3632                                errors: 1u64,
3633                            },
3634                        }
3635                    }
3636                }
3637            }
3638        );
3639
3640        // Remove network 1 -> success.
3641        assert_eq!(process_fidl_request(&mut service, proxy.remove(1)).await, Ok(()));
3642
3643        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3644        assert_data_tree!(
3645            hierarchy,
3646            root: contains {
3647                telemetry: contains {
3648                    operations: contains {
3649                        delegated: contains {
3650                            removes: contains { successes: 1u64, errors: 1u64 },
3651                        }
3652                    }
3653                }
3654            }
3655        );
3656    }
3657
3658    #[fuchsia::test]
3659    async fn test_network_registry_inspect() {
3660        const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
3661        const DELEGATED_ID_100: NetworkId =
3662            NetworkId::Delegated(InterfaceId(NonZeroU64::new(100).unwrap()));
3663
3664        let inspector = fuchsia_inspect::Inspector::default();
3665        let telemetry_node = inspector.root().create_child("telemetry");
3666        let mut service = NetpolNetworksService::default()
3667            .with_inspect(&telemetry_node, "operations", &telemetry_node, "network_registry")
3668            .expect("failed to initialize inspect");
3669        inspector.root().record(telemetry_node);
3670
3671        // Initial check: no networks, reported both as a zero count and as an
3672        // empty network_registry node.
3673        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3674        assert_data_tree!(
3675            hierarchy,
3676            root: contains {
3677                telemetry: contains {
3678                    network_count: 0u64,
3679                    network_registry: {}
3680                }
3681            }
3682        );
3683
3684        // Add a Fuchsia network (ID_2)
3685        service
3686            .update(NetworkRegistryUpdate::ChangeNetwork(
3687                FUCHSIA_ID_2,
3688                NetworkUpdate::Properties(NetworkPropertiesChange {
3689                    added: true,
3690                    name: Some("wlan0".to_string()),
3691                    network_type: Some(fnp_properties::NetworkType::Wifi),
3692                    connectivity_state: Some(fnp_properties::ConnectivityState::FullConnectivity),
3693                    ..Default::default()
3694                }),
3695            ))
3696            .await;
3697
3698        // Add a Delegated network (ID 100)
3699        let mut marks = fnet::Marks::default();
3700        marks.mark_1 = Some(100);
3701        service
3702            .update(NetworkRegistryUpdate::ChangeNetwork(
3703                DELEGATED_ID_100,
3704                NetworkUpdate::Properties(NetworkPropertiesChange {
3705                    added: true,
3706                    delegated: Some(DelegatedNetworkProperties {
3707                        marks: marks.clone(),
3708                        ..Default::default()
3709                    }),
3710                    name: Some("eth0".to_string()),
3711                    network_type: Some(fnp_properties::NetworkType::Ethernet),
3712                    connectivity_state: Some(fnp_properties::ConnectivityState::LocalConnectivity),
3713                    ..Default::default()
3714                }),
3715            ))
3716            .await;
3717
3718        // Make Delegated network Starnix default
3719        service
3720            .update(NetworkRegistryUpdate::ChangeNetwork(
3721                DELEGATED_ID_100,
3722                NetworkUpdate::MakeDefault,
3723            ))
3724            .await;
3725
3726        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3727        assert_data_tree!(
3728            hierarchy,
3729            root: contains {
3730                telemetry: contains {
3731                    network_count: 2u64,
3732                    network_registry: {
3733                        default_network: "fuchsia:2",
3734                        starnix_default: "delegated:100",
3735                        fuchsia_2: {
3736                            name: "wlan0",
3737                            network_type: "Wifi",
3738                            connectivity_state: "FullConnectivity",
3739                        },
3740                        delegated_100: {
3741                            name: "eth0",
3742                            network_type: "Ethernet",
3743                            connectivity_state: "LocalConnectivity",
3744                        },
3745                    }
3746                }
3747            }
3748        );
3749
3750        // Update Delegated network properties (change connectivity_state and name)
3751        service
3752            .update(NetworkRegistryUpdate::ChangeNetwork(
3753                DELEGATED_ID_100,
3754                NetworkUpdate::Properties(NetworkPropertiesChange {
3755                    added: false,
3756                    delegated: Some(DelegatedNetworkProperties { marks, ..Default::default() }),
3757                    name: Some("eth0_updated".to_string()),
3758                    network_type: Some(fnp_properties::NetworkType::Ethernet),
3759                    connectivity_state: Some(fnp_properties::ConnectivityState::FullConnectivity),
3760                    ..Default::default()
3761                }),
3762            ))
3763            .await;
3764
3765        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3766        assert_data_tree!(
3767            hierarchy,
3768            root: contains {
3769                telemetry: contains {
3770                    network_count: 2u64,
3771                    network_registry: {
3772                        default_network: "fuchsia:2",
3773                        starnix_default: "delegated:100",
3774                        fuchsia_2: {
3775                            name: "wlan0",
3776                            network_type: "Wifi",
3777                            connectivity_state: "FullConnectivity",
3778                        },
3779                        delegated_100: {
3780                            name: "eth0_updated",
3781                            network_type: "Ethernet",
3782                            connectivity_state: "FullConnectivity",
3783                        },
3784                    }
3785                }
3786            }
3787        );
3788
3789        // Remove the Fuchsia network (ID_2).
3790        // Default network should now fall back to the delegated network.
3791        service
3792            .update(NetworkRegistryUpdate::ChangeNetwork(FUCHSIA_ID_2, NetworkUpdate::Remove))
3793            .await;
3794
3795        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3796        assert_data_tree!(
3797            hierarchy,
3798            root: contains {
3799                telemetry: contains {
3800                    network_count: 1u64,
3801                    network_registry: {
3802                        default_network: "delegated:100",
3803                        starnix_default: "delegated:100",
3804                        delegated_100: {
3805                            name: "eth0_updated",
3806                            network_type: "Ethernet",
3807                            connectivity_state: "FullConnectivity",
3808                        },
3809                    }
3810                }
3811            }
3812        );
3813
3814        // Unset Starnix default and remove Delegated network.
3815        // network_registry node should now be completely empty.
3816        service.update(NetworkRegistryUpdate::UnsetDefaultNetwork).await;
3817        service
3818            .update(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_100, NetworkUpdate::Remove))
3819            .await;
3820
3821        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
3822        assert_data_tree!(
3823            hierarchy,
3824            root: contains {
3825                telemetry: contains {
3826                    network_count: 0u64,
3827                    network_registry: {}
3828                }
3829            }
3830        );
3831    }
3832
3833    #[fuchsia::test]
3834    async fn test_reachability_service_integration() {
3835        let mut service = NetpolNetworksService::default();
3836
3837        let mut expected_disconnected = freachability::Snapshot::default();
3838        expected_disconnected.gateway_reachable = Some(false);
3839        expected_disconnected.internet_available = Some(false);
3840        expected_disconnected.dns_active = Some(false);
3841        expected_disconnected.http_active = Some(false);
3842
3843        let mut expected_validated = freachability::Snapshot::default();
3844        expected_validated.gateway_reachable = Some(true);
3845        expected_validated.internet_available = Some(true);
3846        expected_validated.dns_active = Some(true);
3847        expected_validated.http_active = Some(true);
3848
3849        let (proxy, stream) =
3850            fidl::endpoints::create_proxy_and_stream::<freachability::MonitorMarker>();
3851        service.add_stream(stream);
3852        assert_eq!(service.reachability_watcher_count(), 1);
3853
3854        let watch_fut = proxy.watch();
3855        let event = service.select_next_some().await;
3856        assert_eq!(
3857            service.handle_event(event).await.expect("handle event"),
3858            DelegatedNetworkUpdateResult::default()
3859        );
3860        let snapshot = watch_fut.await.expect("watch error");
3861        assert_eq!(snapshot, expected_disconnected);
3862
3863        let mut second_watch = proxy.watch();
3864        let event = service.select_next_some().await;
3865        assert_eq!(
3866            service.handle_event(event).await.expect("handle event"),
3867            DelegatedNetworkUpdateResult::default()
3868        );
3869        assert_matches!(futures::poll!(&mut second_watch), std::task::Poll::Pending);
3870
3871        service
3872            .update(NetworkRegistryUpdate::ChangeNetwork(
3873                DELEGATED_ID_1,
3874                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3875                    true,
3876                    Some(test_marks()),
3877                    None,
3878                    Some(fnp_properties::ConnectivityState::FullConnectivity),
3879                )),
3880            ))
3881            .await;
3882        service
3883            .update(NetworkRegistryUpdate::ChangeNetwork(
3884                DELEGATED_ID_1,
3885                NetworkUpdate::MakeDefault,
3886            ))
3887            .await;
3888
3889        let snapshot = second_watch.await.expect("watch should succeed");
3890        assert_eq!(snapshot, expected_validated);
3891    }
3892
3893    #[fuchsia::test]
3894    async fn test_networks_client_disconnect_cleans_up_state() {
3895        let mut service = NetpolNetworksService::default();
3896        let (proxy, stream) =
3897            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3898        service.add_stream(stream);
3899
3900        // Client calls WatchDefault when there is no default network (so it hangs).
3901        let mut watch_default_fut = proxy.watch_default();
3902        let event = service.select_next_some().await;
3903        assert_eq!(
3904            service.handle_event(event).await.expect("Failed to handle event"),
3905            DelegatedNetworkUpdateResult::default()
3906        );
3907        assert_matches!((&mut watch_default_fut).now_or_never(), None);
3908        assert_matches!(
3909            &service.networks_clients.values().collect::<Vec<_>>()[..],
3910            [NetworksClient { responder: Some(_), .. }]
3911        );
3912
3913        // Client closes the connection.
3914        drop(watch_default_fut);
3915        drop(proxy);
3916
3917        let event = service.select_next_some().await;
3918        assert_eq!(
3919            service.handle_event(event).await.expect("Failed to handle event"),
3920            DelegatedNetworkUpdateResult::default()
3921        );
3922        // Client state and connection generations must be cleaned up.
3923        assert!(service.networks_clients.is_empty());
3924        assert!(service.generations_by_connection.default_network.is_empty());
3925    }
3926
3927    #[fuchsia::test]
3928    async fn test_property_watcher_client_disconnect_cleans_up_state() {
3929        let mut service = NetpolNetworksService::default();
3930
3931        service
3932            .update(NetworkRegistryUpdate::ChangeNetwork(
3933                DELEGATED_ID_1,
3934                NetworkUpdate::Properties(DELEGATED_NET_1.change_with(
3935                    true,
3936                    Some(test_marks()),
3937                    None,
3938                    None,
3939                )),
3940            ))
3941            .await;
3942
3943        let token = service
3944            .tokens
3945            .ensure_token(NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
3946            .get()
3947            .duplicate()
3948            .unwrap();
3949
3950        let (watcher, server_end) =
3951            fidl::endpoints::create_proxy::<fnp_properties::PropertyWatcherMarker>();
3952        let (networks_proxy, networks_stream) =
3953            fidl::endpoints::create_proxy_and_stream::<fnp_properties::NetworksMarker>();
3954        service.add_stream(networks_stream);
3955
3956        let watch_req =
3957            networks_proxy.watch_properties(fnp_properties::NetworksWatchPropertiesRequest {
3958                network: Some(token),
3959                properties: Some(fnp_properties::PropertyInterest::SOCKET_MARKS),
3960                watcher: Some(server_end),
3961                ..Default::default()
3962            });
3963
3964        let req_event = service.select_next_some().await;
3965        assert_eq!(
3966            service.handle_event(req_event).await.expect("Failed to handle event"),
3967            DelegatedNetworkUpdateResult::default()
3968        );
3969        assert_matches!(watch_req.await, Ok(Ok(())));
3970
3971        // Consume initial snapshot.
3972        let watch_fut1 = watcher.watch();
3973        let pw_event1 = service.select_next_some().await;
3974        assert_eq!(
3975            service.handle_event(pw_event1).await.expect("Failed to handle event"),
3976            DelegatedNetworkUpdateResult::default()
3977        );
3978        let _ = watch_fut1.await.unwrap().unwrap();
3979
3980        // Second watch hangs waiting for updates.
3981        let mut watch_fut2 = watcher.watch();
3982        let pw_event2 = service.select_next_some().await;
3983        assert_eq!(
3984            service.handle_event(pw_event2).await.expect("Failed to handle event"),
3985            DelegatedNetworkUpdateResult::default()
3986        );
3987        assert_matches!((&mut watch_fut2).now_or_never(), None);
3988        assert_eq!(service.property_watchers.len(), 1);
3989
3990        // Client closes the PropertyWatcher connection.
3991        drop(watch_fut2);
3992        drop(watcher);
3993
3994        let disconnect_event = service.select_next_some().await;
3995        assert_eq!(
3996            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
3997            DelegatedNetworkUpdateResult::default()
3998        );
3999
4000        // Watcher registration and connection generation must be cleanly purged.
4001        assert!(service.property_watchers.is_empty());
4002        assert!(service.generations_by_connection.properties.is_empty());
4003    }
4004    #[fuchsia::test]
4005    async fn test_delegated_networks_single_connection() {
4006        let mut service = NetpolNetworksService::default();
4007        let (proxy1, stream1) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
4008        service.add_stream(stream1);
4009        assert!(service.delegated_networks_client.is_some());
4010
4011        // First connection succeeds.
4012        assert_eq!(
4013            process_fidl_request(&mut service, proxy1.remove(1)).await,
4014            Err(NetworkRegistryRemoveError::NotFound)
4015        );
4016
4017        // Concurrent second connection is rejected with ALREADY_EXISTS.
4018        let (proxy2, stream2) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
4019        service.add_stream(stream2);
4020        assert_matches!(
4021            proxy2.remove(1).await,
4022            Err(fidl::Error::ClientChannelClosed { epitaph, .. })
4023                if epitaph == zx::Status::ALREADY_EXISTS
4024        );
4025
4026        // First connection continues to work after the second connection is rejected.
4027        assert_eq!(
4028            process_fidl_request(&mut service, proxy1.remove(1)).await,
4029            Err(NetworkRegistryRemoveError::NotFound)
4030        );
4031
4032        // Once the first connection closes and its terminal item is processed,
4033        // a new connection is allowed.
4034        drop(proxy1);
4035        let disconnect_event = service.select_next_some().await;
4036        assert_eq!(
4037            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
4038            DelegatedNetworkUpdateResult::default()
4039        );
4040        assert!(service.delegated_networks_client.is_none());
4041
4042        let (proxy3, stream3) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
4043        service.add_stream(stream3);
4044        assert_eq!(
4045            process_fidl_request(&mut service, proxy3.remove(1)).await,
4046            Err(NetworkRegistryRemoveError::NotFound)
4047        );
4048    }
4049
4050    #[fuchsia::test]
4051    async fn test_delegated_networks_client_disconnect_cleans_up_state() {
4052        let mut service = NetpolNetworksService::default();
4053        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
4054        service.add_stream(stream);
4055
4056        // The raw id used by the `fnp_properties::Network` as a `network_id`.
4057        const STARNIX_ID_1: u32 = 1;
4058        const STARNIX_ID_2: u32 = 2;
4059        const SOCKET_MARK_1: u32 = 100;
4060        const SOCKET_MARK_2: u32 = 200;
4061
4062        // Register a native Fuchsia network alongside two delegated networks.
4063        service
4064            .update(NetworkRegistryUpdate::ChangeNetwork(
4065                FUCHSIA_ID_1,
4066                NetworkUpdate::Properties(NetworkPropertiesChange {
4067                    added: true,
4068                    ..Default::default()
4069                }),
4070            ))
4071            .await;
4072
4073        assert_eq!(
4074            process_fidl_request(
4075                &mut service,
4076                proxy.add(&starnix_network_payload(STARNIX_ID_1, SOCKET_MARK_1))
4077            )
4078            .await,
4079            Ok(())
4080        );
4081        assert_eq!(
4082            process_fidl_request(
4083                &mut service,
4084                proxy.add(&starnix_network_payload(STARNIX_ID_2, SOCKET_MARK_2))
4085            )
4086            .await,
4087            Ok(())
4088        );
4089        assert_eq!(
4090            process_fidl_request(
4091                &mut service,
4092                proxy.set_default(&fposix_socket::OptionalUint32::Value(STARNIX_ID_1)),
4093            )
4094            .await,
4095            Ok(())
4096        );
4097        assert_eq!(service.network_registry.starnix_default, Some(DELEGATED_ID_1));
4098        assert_eq!(service.network_registry.networks.len(), 3);
4099
4100        // Disconnect the client and verify all delegated state is cleared while Fuchsia state
4101        // remains intact.
4102        drop(proxy);
4103        let disconnect_event = service.select_next_some().await;
4104        assert_eq!(
4105            service.handle_event(disconnect_event).await.expect("Failed to handle event"),
4106            DelegatedNetworkUpdateResult { dns_servers: Some(vec![]) }
4107        );
4108        assert_eq!(service.network_registry.starnix_default, None);
4109        assert_eq!(
4110            service.network_registry.networks.keys().copied().collect::<Vec<_>>(),
4111            vec![FUCHSIA_ID_1]
4112        );
4113
4114        // A reconnecting client can re-add NETWORK_ID_1 without hitting DuplicateNetworkId.
4115        let (proxy2, stream2) = fidl::endpoints::create_proxy_and_stream::<NetworkRegistryMarker>();
4116        service.add_stream(stream2);
4117        assert_eq!(
4118            process_fidl_request(
4119                &mut service,
4120                proxy2.add(&starnix_network_payload(STARNIX_ID_1, SOCKET_MARK_2)),
4121            )
4122            .await,
4123            Ok(())
4124        );
4125    }
4126
4127    #[fuchsia::test]
4128    async fn test_networks_and_dns_inspect() {
4129        const FUCHSIA_ID_2: NetworkId = NetworkId::Fuchsia(ID_2);
4130        const DELEGATED_ID_100: NetworkId =
4131            NetworkId::Delegated(InterfaceId(NonZeroU64::new(100).unwrap()));
4132
4133        let inspector = fuchsia_inspect::Inspector::default();
4134        let telemetry_node = inspector.root().create_child("telemetry");
4135        let mut service = NetpolNetworksService::default()
4136            .with_inspect(&telemetry_node, "operations", &telemetry_node, "network_registry")
4137            .expect("failed to initialize inspect");
4138        inspector.root().record(telemetry_node);
4139
4140        // Initial check: empty network_registry node
4141        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
4142        assert_data_tree!(
4143            hierarchy,
4144            root: contains {
4145                telemetry: contains {
4146                    network_registry: {}
4147                }
4148            }
4149        );
4150
4151        // Update DNS with static and dynamic (DHCPv4, NDP, DHCPv6) servers.
4152        let static_server_1 = fnet_name::DnsServer_ {
4153            address: Some(net_declare::fidl_socket_addr!("8.8.8.8:53")),
4154            source: Some(fnet_name::DnsServerSource::StaticSource(
4155                fnet_name::StaticDnsServerSource::default(),
4156            )),
4157            ..Default::default()
4158        };
4159        let static_server_2 = fnet_name::DnsServer_ {
4160            address: Some(net_declare::fidl_socket_addr!("8.8.4.4:53")),
4161            source: Some(fnet_name::DnsServerSource::StaticSource(
4162                fnet_name::StaticDnsServerSource::default(),
4163            )),
4164            ..Default::default()
4165        };
4166        let dhcp_server = fnet_name::DnsServer_ {
4167            address: Some(net_declare::fidl_socket_addr!("192.168.1.1:53")),
4168            source: Some(fnet_name::DnsServerSource::Dhcp(fnet_name::DhcpDnsServerSource {
4169                source_interface: Some(ID_2.get()),
4170                ..Default::default()
4171            })),
4172            ..Default::default()
4173        };
4174        let ndp_server = fnet_name::DnsServer_ {
4175            address: Some(net_declare::fidl_socket_addr!("[fe80::1%1]:53")),
4176            source: Some(fnet_name::DnsServerSource::Ndp(fnet_name::NdpDnsServerSource {
4177                source_interface: Some(ID_2.get()),
4178                ..Default::default()
4179            })),
4180            ..Default::default()
4181        };
4182        let dhcpv6_server = fnet_name::DnsServer_ {
4183            address: Some(net_declare::fidl_socket_addr!("[2001:4860:4860::8888]:53")),
4184            source: Some(fnet_name::DnsServerSource::Dhcpv6(fnet_name::Dhcpv6DnsServerSource {
4185                source_interface: Some(ID_2.get()),
4186                ..Default::default()
4187            })),
4188            ..Default::default()
4189        };
4190
4191        service
4192            .update(NetworkRegistryUpdate::UpdateDns(vec![
4193                dhcp_server.clone(),
4194                ndp_server.clone(),
4195                dhcpv6_server.clone(),
4196                static_server_1.clone(),
4197                static_server_2.clone(),
4198            ]))
4199            .await;
4200
4201        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
4202        assert_data_tree!(
4203            hierarchy,
4204            root: contains {
4205                telemetry: contains {
4206                    network_registry: contains {
4207                        system_dns_servers: {
4208                            "0": {
4209                                server: "192.168.1.1:53",
4210                                type: "Dhcpv4",
4211                                interface_id: 2u64,
4212                            },
4213                            "1": {
4214                                server: "[fe80::1%1]:53",
4215                                type: "Ndp",
4216                                interface_id: 2u64,
4217                            },
4218                            "2": {
4219                                server: "[2001:4860:4860::8888]:53",
4220                                type: "Dhcpv6",
4221                                interface_id: 2u64,
4222                            },
4223                            "3": {
4224                                server: "8.8.8.8:53",
4225                                type: "Static",
4226                            },
4227                            "4": {
4228                                server: "8.8.4.4:53",
4229                                type: "Static",
4230                            },
4231                        },
4232                    }
4233                }
4234            }
4235        );
4236
4237        // Add a Fuchsia network (ID_2)
4238        service
4239            .update(NetworkRegistryUpdate::ChangeNetwork(
4240                FUCHSIA_ID_2,
4241                NetworkUpdate::Properties(NetworkPropertiesChange {
4242                    added: true,
4243                    name: Some("wlan0".to_string()),
4244                    network_type: Some(fnp_properties::NetworkType::Wifi),
4245                    connectivity_state: Some(fnp_properties::ConnectivityState::FullConnectivity),
4246                    ..Default::default()
4247                }),
4248            ))
4249            .await;
4250
4251        // Add a Delegated network (ID 100) with SocketProxy DNS.
4252        let socketproxy_dns = fnet_name::DnsServer_ {
4253            address: Some(net_declare::fidl_socket_addr!("192.168.1.2:53")),
4254            source: Some(fnet_name::DnsServerSource::SocketProxy(
4255                fnet_name::SocketProxyDnsServerSource::default(),
4256            )),
4257            ..Default::default()
4258        };
4259        let mut marks = fnet::Marks::default();
4260        marks.mark_1 = Some(100);
4261        service
4262            .update(NetworkRegistryUpdate::ChangeNetwork(
4263                DELEGATED_ID_100,
4264                NetworkUpdate::Properties(NetworkPropertiesChange {
4265                    added: true,
4266                    delegated: Some(DelegatedNetworkProperties { marks, ..Default::default() }),
4267                    dns_servers: Some(vec![socketproxy_dns]),
4268                    name: Some("wlan0".to_string()),
4269                    network_type: Some(fnp_properties::NetworkType::Wifi),
4270                    connectivity_state: Some(fnp_properties::ConnectivityState::FullConnectivity),
4271                }),
4272            ))
4273            .await;
4274
4275        let hierarchy = fuchsia_inspect::reader::read(&inspector).await.unwrap();
4276        assert_data_tree!(
4277            hierarchy,
4278            root: contains {
4279                telemetry: contains {
4280                    network_registry: {
4281                        default_network: "fuchsia:2",
4282                        system_dns_servers: {
4283                            "0": {
4284                                server: "192.168.1.1:53",
4285                                type: "Dhcpv4",
4286                                interface_id: 2u64,
4287                            },
4288                            "1": {
4289                                server: "[fe80::1%1]:53",
4290                                type: "Ndp",
4291                                interface_id: 2u64,
4292                            },
4293                            "2": {
4294                                server: "[2001:4860:4860::8888]:53",
4295                                type: "Dhcpv6",
4296                                interface_id: 2u64,
4297                            },
4298                            "3": {
4299                                server: "8.8.8.8:53",
4300                                type: "Static",
4301                            },
4302                            "4": {
4303                                server: "8.8.4.4:53",
4304                                type: "Static",
4305                            },
4306                        },
4307                        fuchsia_2: {
4308                            name: "wlan0",
4309                            network_type: "Wifi",
4310                            connectivity_state: "FullConnectivity",
4311                            dns_servers: {
4312                                "0": {
4313                                    server: "192.168.1.1:53",
4314                                    type: "Dhcpv4",
4315                                    interface_id: 2u64,
4316                                },
4317                                "1": {
4318                                    server: "[fe80::1%1]:53",
4319                                    type: "Ndp",
4320                                    interface_id: 2u64,
4321                                },
4322                                "2": {
4323                                    server: "[2001:4860:4860::8888]:53",
4324                                    type: "Dhcpv6",
4325                                    interface_id: 2u64,
4326                                },
4327                            },
4328                        },
4329                        delegated_100: {
4330                            name: "wlan0",
4331                            network_type: "Wifi",
4332                            connectivity_state: "FullConnectivity",
4333                            dns_servers: {
4334                                "0": {
4335                                    server: "192.168.1.2:53",
4336                                    type: "SocketProxy",
4337                                },
4338                            },
4339                        },
4340                    }
4341                }
4342            }
4343        );
4344    }
4345
4346    async fn drive_request<T>(
4347        service: &mut NetpolNetworksService,
4348        request_future: impl Future<Output = Result<T, fidl::Error>>,
4349    ) -> T {
4350        let event = service.select_next_some().await;
4351        assert_eq!(
4352            service.handle_event(event).await.expect("failed to handle event"),
4353            DelegatedNetworkUpdateResult::default()
4354        );
4355        request_future.await.expect("FIDL call failed")
4356    }
4357
4358    #[fuchsia::test]
4359    async fn test_lookup_network() {
4360        let mut service = NetpolNetworksService::default();
4361        let (lookup_proxy, lookup_stream) = fidl::endpoints::create_proxy_and_stream::<
4362            fnp_properties::PrivilegedNetworkLookupMarker,
4363        >();
4364        service.add_stream(lookup_stream);
4365
4366        const STARNIX_HANDLE: u64 = 0x1111;
4367        const STARNIX_HANDLE_UPDATED: u64 = 0x2222;
4368
4369        let set_handle = |added: bool, starnix_handle: u64| {
4370            NetworkRegistryUpdate::ChangeNetwork(
4371                DELEGATED_ID_1,
4372                NetworkUpdate::Properties(NetworkPropertiesChange {
4373                    added,
4374                    delegated: Some(DelegatedNetworkProperties {
4375                        marks: test_marks(),
4376                        starnix_handle,
4377                    }),
4378                    ..Default::default()
4379                }),
4380            )
4381        };
4382        let lookup = |handle: u64| {
4383            lookup_proxy.lookup(&fnp_properties::LookupRequest::StarnixHandle(handle))
4384        };
4385
4386        // Lookup before any networks are added returns NotFound.
4387        assert_eq!(
4388            drive_request(&mut service, lookup(STARNIX_HANDLE)).await,
4389            Err(fnp_properties::LookupError::NotFound)
4390        );
4391
4392        // Add DELEGATED_ID_1 as a non-default network and look up its token by STARNIX_HANDLE.
4393        service.update(set_handle(true, STARNIX_HANDLE)).await;
4394        let token_1 = drive_request(&mut service, lookup(STARNIX_HANDLE)).await.unwrap();
4395        assert_eq!(
4396            service.tokens.get_contents(&token_1),
4397            Ok(&NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
4398        );
4399
4400        // Updating DELEGATED_ID_1's handle causes future lookups by STARNIX_HANDLE to return
4401        // NotFound and lookups by STARNIX_HANDLE_UPDATED to return a token with the same KOID,
4402        // while the previously obtained `token_1` remains active.
4403        service.update(set_handle(false, STARNIX_HANDLE_UPDATED)).await;
4404        assert_eq!(
4405            drive_request(&mut service, lookup(STARNIX_HANDLE)).await,
4406            Err(fnp_properties::LookupError::NotFound)
4407        );
4408        assert_eq!(
4409            service.tokens.get_contents(&token_1),
4410            Ok(&NetworkTokenContents { network_id: DELEGATED_ID_1, is_default: false })
4411        );
4412        let token_1_updated =
4413            drive_request(&mut service, lookup(STARNIX_HANDLE_UPDATED)).await.unwrap();
4414        assert_eq!(token_1.koid().unwrap(), token_1_updated.koid().unwrap());
4415
4416        // Removing DELEGATED_ID_1 invalidates `token_1` and causes Lookup to return NotFound.
4417        service
4418            .update(NetworkRegistryUpdate::ChangeNetwork(DELEGATED_ID_1, NetworkUpdate::Remove))
4419            .await;
4420        assert_eq!(service.tokens.get_contents(&token_1), Err(zx::Status::NOT_FOUND));
4421        assert_eq!(
4422            drive_request(&mut service, lookup(STARNIX_HANDLE_UPDATED)).await,
4423            Err(fnp_properties::LookupError::NotFound)
4424        );
4425    }
4426}