blob: 0d097ccb45f66037e36a6d7ebde35c86cae8b296 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::hash_map::HashMap;
use ethernet as eth;
use fidl_fuchsia_hardware_ethernet::Features;
use net_types::{ethernet::Mac, UnicastAddr};
use netstack3_core::{DeviceId, Entry, IdMapCollection, IdMapCollectionKey};
pub const LOOPBACK_MAC: Mac = Mac::new([0, 0, 0, 0, 0, 0]);
pub type BindingId = u64;
/// Keeps tabs on devices.
///
/// `Devices` keeps a list of devices that are installed in the netstack with
/// an associated netstack core ID `C` used to reference the device.
///
/// The type parameters `C` and `I` are for the core ID type and the extra
/// information associated with the device, respectively, and default to the
/// types used by `EventLoop` for brevity in the main use case. The type
/// parameters are there to allow testing without dependencies on `core`.
pub struct Devices<C: IdMapCollectionKey = DeviceId, I = DeviceSpecificInfo> {
devices: IdMapCollection<C, DeviceInfo<C, I>>,
// invariant: all values in id_map are valid keys in devices.
id_map: HashMap<BindingId, C>,
last_id: BindingId,
}
impl<C: IdMapCollectionKey, I> Default for Devices<C, I> {
fn default() -> Self {
Self { devices: IdMapCollection::new(), id_map: HashMap::new(), last_id: 0 }
}
}
impl<C, I> Devices<C, I>
where
C: IdMapCollectionKey + Clone + std::fmt::Debug,
{
/// Allocates a new [`BindingId`].
fn alloc_id(last_id: &mut BindingId) -> BindingId {
*last_id += 1;
*last_id
}
/// Adds a new device.
///
/// Adds a new device if the informed `core_id` is valid (i.e., not
/// currently tracked by [`Devices`]). A new [`BindingId`] will be allocated
/// and a [`DeviceInfo`] struct will be created with the provided `info` and
/// IDs.
pub fn add_device<F: FnOnce(BindingId) -> I>(
&mut self,
core_id: C,
info: F,
) -> Option<BindingId> {
let Self { devices, id_map, last_id } = self;
match devices.entry(core_id) {
Entry::Occupied(_) => None,
Entry::Vacant(entry) => {
let id = Self::alloc_id(last_id);
let core_id = entry.key().clone();
assert_matches::assert_matches!(id_map.insert(id, core_id.clone()), None);
let _: &mut DeviceInfo<_, _> =
entry.insert(DeviceInfo { id, core_id: core_id, info: info(id) });
Some(id)
}
}
}
/// Removes a device from the internal list.
///
/// Removes a device from the internal [`Devices`] list and returns the
/// associated [`DeviceInfo`] if `id` is found or `None` otherwise.
pub fn remove_device(&mut self, id: BindingId) -> Option<DeviceInfo<C, I>> {
let Self { devices, id_map, last_id: _ } = self;
id_map.remove(&id).and_then(|core_id| devices.remove(&core_id))
}
/// Gets an iterator over all tracked devices.
#[cfg(test)]
pub fn iter_devices(&self) -> impl Iterator<Item = &DeviceInfo<C, I>> {
self.devices.iter()
}
/// Retrieve device with [`BindingId`].
pub fn get_device(&self, id: BindingId) -> Option<&DeviceInfo<C, I>> {
let Self { devices, id_map, last_id: _ } = self;
id_map.get(&id).and_then(|device_id| devices.get(&device_id))
}
/// Retrieve mutable reference to device with [`BindingId`].
pub fn get_device_mut(&mut self, id: BindingId) -> Option<&mut DeviceInfo<C, I>> {
let Self { devices, id_map, last_id: _ } = self;
id_map.get(&id).and_then(move |core_id| devices.get_mut(&core_id))
}
/// Retrieve associated `core_id` for [`BindingId`].
pub fn get_core_id(&self, id: BindingId) -> Option<C> {
self.id_map.get(&id).cloned()
}
/// Retrieve non-mutable reference to device by associated [`CoreId`] `id`.
pub fn get_core_device(&self, id: C) -> Option<&DeviceInfo<C, I>> {
self.devices.get(&id)
}
/// Retrieve mutable reference to device by associated [`CoreId`] `id`.
pub fn get_core_device_mut(&mut self, id: C) -> Option<&mut DeviceInfo<C, I>> {
self.devices.get_mut(&id)
}
/// Retrieve associated `binding_id` for `core_id`.
pub fn get_binding_id(&self, core_id: C) -> Option<BindingId> {
self.devices.get(&core_id).map(|d| d.id)
}
}
/// Device specific iformation.
#[derive(Debug)]
pub enum DeviceSpecificInfo {
Ethernet(EthernetInfo),
Netdevice(NetdeviceInfo),
Loopback(LoopbackInfo),
}
impl DeviceSpecificInfo {
pub fn common_info(&self) -> &CommonInfo {
match self {
Self::Ethernet(i) => &i.common_info,
Self::Netdevice(i) => &i.common_info,
Self::Loopback(i) => &i.common_info,
}
}
pub fn common_info_mut(&mut self) -> &mut CommonInfo {
match self {
Self::Ethernet(i) => &mut i.common_info,
Self::Netdevice(i) => &mut i.common_info,
Self::Loopback(i) => &mut i.common_info,
}
}
}
/// Information common to all devices.
#[derive(Debug)]
pub struct CommonInfo {
pub mtu: u32,
pub admin_enabled: bool,
pub events: super::InterfaceEventProducer,
pub name: String,
}
/// Loopback device information.
#[derive(Debug)]
pub struct LoopbackInfo {
pub common_info: CommonInfo,
}
/// Ethernet device information.
#[derive(Debug)]
pub struct EthernetInfo {
pub common_info: CommonInfo,
pub client: eth::Client,
pub mac: UnicastAddr<Mac>,
pub features: Features,
pub phy_up: bool,
}
impl From<EthernetInfo> for DeviceSpecificInfo {
fn from(i: EthernetInfo) -> Self {
Self::Ethernet(i)
}
}
/// Network device information.
#[derive(Debug)]
pub struct NetdeviceInfo {
pub common_info: CommonInfo,
pub handler: super::netdevice_worker::PortHandler,
pub mac: UnicastAddr<Mac>,
pub phy_up: bool,
}
impl From<NetdeviceInfo> for DeviceSpecificInfo {
fn from(i: NetdeviceInfo) -> Self {
Self::Netdevice(i)
}
}
/// Device information kept by [`Devices`].
#[derive(Debug, PartialEq)]
pub struct DeviceInfo<C = DeviceId, I = DeviceSpecificInfo> {
id: BindingId,
core_id: C,
info: I,
}
impl<C, I> DeviceInfo<C, I>
where
C: Clone,
{
pub fn core_id(&self) -> C {
self.core_id.clone()
}
pub fn info(&self) -> &I {
&self.info
}
pub fn into_info(self) -> I {
self.info
}
pub fn info_mut(&mut self) -> &mut I {
&mut self.info
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use std::collections::HashSet;
use super::*;
type TestDevices = Devices<MockDeviceId, u64>;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct MockDeviceId(usize);
impl IdMapCollectionKey for MockDeviceId {
const VARIANT_COUNT: usize = 1;
fn get_variant(&self) -> usize {
0
}
fn get_id(&self) -> usize {
self.0 as usize
}
}
#[test]
fn test_add_remove_device() {
let mut d = TestDevices::default();
let core_a = MockDeviceId(1);
let core_b = MockDeviceId(2);
let a = d.add_device(core_a, |id| id + 10).expect("can add device");
let b = d.add_device(core_b, |id| id + 20).expect("can add device");
assert_ne!(a, b, "allocated same id");
assert_eq!(d.add_device(core_a, |id| id + 10), None, "can't add same id again");
// check that ids are incrementing
assert_eq!(d.last_id, 2);
// check that devices are correctly inserted and carry the core id.
assert_eq!(d.get_device(a).unwrap().core_id, core_a);
assert_eq!(d.get_device(b).unwrap().core_id, core_b);
assert_eq!(d.get_core_id(a).unwrap(), core_a);
assert_eq!(d.get_core_id(b).unwrap(), core_b);
assert_eq!(d.get_binding_id(core_a).unwrap(), a);
assert_eq!(d.get_binding_id(core_b).unwrap(), b);
// check that we can retrieve both devices by the core id:
assert_matches!(d.get_core_device_mut(core_a), Some(_));
assert_matches!(d.get_core_device_mut(core_b), Some(_));
// remove both devices
let info_a = d.remove_device(a).expect("can remove device");
let info_b = d.remove_device(b).expect("can remove device");
assert_eq!(info_a.info, a + 10);
assert_eq!(info_b.info, b + 20);
assert_eq!(info_a.core_id, core_a);
assert_eq!(info_b.core_id, core_b);
// removing device again will fail
assert_eq!(d.remove_device(a), None);
// retrieving the devices now should fail:
assert_eq!(d.get_device(a), None);
assert_eq!(d.get_core_id(a), None);
assert_eq!(d.get_core_device_mut(core_a), None);
assert!(d.devices.is_empty());
assert!(d.id_map.is_empty());
}
#[test]
fn test_iter() {
let mut d = TestDevices::default();
let core_a = MockDeviceId(1);
let a = d.add_device(core_a, |id| id + 10).unwrap();
assert_eq!(d.iter_devices().map(|d| d.id).collect::<HashSet<_>>(), HashSet::from([a]));
let core_b = MockDeviceId(2);
let b = d.add_device(core_b, |id| id + 20).unwrap();
assert_eq!(d.iter_devices().map(|d| d.id).collect::<HashSet<_>>(), HashSet::from([a, b]));
}
}