diff --git a/network-hypervisor/src/vl2/topology.rs b/network-hypervisor/src/vl2/topology.rs index 3154f96f6..3defd1233 100644 --- a/network-hypervisor/src/vl2/topology.rs +++ b/network-hypervisor/src/vl2/topology.rs @@ -1,3 +1,61 @@ -pub struct Member {} +use std::borrow::Cow; -pub struct Topology {} +use zerotier_utils::blob::Blob; +use zerotier_utils::flatsortedmap::FlatSortedMap; + +use serde::{Deserialize, Serialize}; + +use crate::vl1::identity::IDENTITY_FINGERPRINT_SIZE; +use crate::vl1::inetaddress::InetAddress; +use crate::vl2::rule::Rule; + +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct Member<'a> { + #[serde(skip_serializing_if = "u64_zero")] + #[serde(default)] + pub flags: u64, + + #[serde(skip_serializing_if = "cow_str_is_empty")] + #[serde(default)] + pub name: Cow<'a, str>, +} + +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct Topology<'a> { + pub timestamp: i64, + + #[serde(skip_serializing_if = "cow_str_is_empty")] + #[serde(default)] + pub name: Cow<'a, str>, + + #[serde(skip_serializing_if = "slice_is_empty")] + #[serde(default)] + pub rules: Cow<'a, [Rule]>, + + #[serde(skip_serializing_if = "FlatSortedMap::is_empty")] + #[serde(default)] + pub dns_resolvers: FlatSortedMap<'a, Cow<'a, str>, InetAddress>, + + #[serde(skip_serializing_if = "FlatSortedMap::is_empty")] + #[serde(default)] + pub dns_names: FlatSortedMap<'a, Cow<'a, str>, InetAddress>, + + #[serde(skip_serializing_if = "FlatSortedMap::is_empty")] + #[serde(default)] + pub members: FlatSortedMap<'a, Blob, Member<'a>>, +} + +#[inline(always)] +fn u64_zero(i: &u64) -> bool { + *i == 0 +} + +#[inline(always)] +fn cow_str_is_empty<'a>(s: &Cow<'a, str>) -> bool { + s.is_empty() +} + +#[inline(always)] +fn slice_is_empty>(x: &S) -> bool { + x.as_ref().is_empty() +} diff --git a/network-hypervisor/src/vl2/v1/networkconfig.rs b/network-hypervisor/src/vl2/v1/networkconfig.rs index d4ae5193b..0e114fc0a 100644 --- a/network-hypervisor/src/vl2/v1/networkconfig.rs +++ b/network-hypervisor/src/vl2/v1/networkconfig.rs @@ -31,11 +31,6 @@ pub struct NetworkConfig { #[serde(default)] pub name: String, - /// A human-readable message for members of this network (V2 only) - #[serde(skip_serializing_if = "String::is_empty")] - #[serde(default)] - pub motd: String, - /// True if network has access control (the default) pub private: bool, @@ -95,7 +90,6 @@ impl NetworkConfig { network_id, issued_to, name: String::new(), - motd: String::new(), private: true, timestamp: 0, mtu: 0, diff --git a/utils/src/blob.rs b/utils/src/blob.rs index b02f7902b..660b35947 100644 --- a/utils/src/blob.rs +++ b/utils/src/blob.rs @@ -7,6 +7,7 @@ */ use std::fmt::Debug; +use std::hash::Hash; use serde::ser::SerializeTuple; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -72,6 +73,27 @@ impl ToString for Blob { } } +impl PartialOrd for Blob { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for Blob { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl Hash for Blob { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + impl Debug for Blob { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -118,7 +140,7 @@ impl<'de, const L: usize> serde::de::Visitor<'de> for BlobVisitor { impl<'de, const L: usize> Deserialize<'de> for Blob { #[inline] - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { diff --git a/utils/src/flatsortedmap.rs b/utils/src/flatsortedmap.rs new file mode 100644 index 000000000..d05e62b3e --- /dev/null +++ b/utils/src/flatsortedmap.rs @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::borrow::Cow; +use std::iter::{FromIterator, Iterator}; + +use serde::{Deserialize, Serialize}; + +/// A simple flat sorted map backed by a vector and binary search. +/// +/// This doesn't support gradual adding of keys or removal of keys, but only construction +/// from an iterator of keys and values. It also implements Serialize and Deserialize and +/// is mainly intended for memory and space efficient serializable lookup tables. +/// +/// If the iterator supplies more than one key with different values, which of these is +/// included is undefined. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +#[repr(transparent)] +pub struct FlatSortedMap<'a, K: Eq + Ord + Clone, V: Clone>(Cow<'a, [(K, V)]>); + +impl<'a, K: Eq + Ord + Clone, V: Clone> FromIterator<(K, V)> for FlatSortedMap<'a, K, V> { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut tmp = Vec::from_iter(iter); + tmp.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + tmp.dedup_by(|a, b| a.0.eq(&b.0)); + Self(Cow::Owned(tmp)) + } +} + +impl<'a, K: Eq + Ord + Clone, V: Clone> Default for FlatSortedMap<'a, K, V> { + #[inline(always)] + fn default() -> Self { + Self(Cow::Owned(Vec::new())) + } +} + +impl<'a, K: Eq + Ord + Clone, V: Clone> FlatSortedMap<'a, K, V> { + #[inline] + pub fn get(&self, k: &K) -> Option<&V> { + if let Ok(idx) = self.0.binary_search_by(|a| a.0.cmp(k)) { + Some(unsafe { &self.0.get_unchecked(idx).1 }) + } else { + None + } + } + + #[inline] + pub fn contains(&self, k: &K) -> bool { + self.0.binary_search_by(|a| a.0.cmp(k)).is_ok() + } + + /// Returns true if this map is valid, meaning that it contains only one of each key and is sorted. + #[inline] + pub fn is_valid(&self) -> bool { + let l = self.0.len(); + if l > 1 { + for i in 1..l { + if unsafe { !self.0.get_unchecked(i - 1).0.cmp(&self.0.get_unchecked(i).0).is_lt() } { + return false; + } + } + } + return true; + } + + #[inline(always)] + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 8f211d91e..cb1e278e9 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -14,6 +14,7 @@ pub mod dictionary; pub mod error; #[allow(unused)] pub mod exitcode; +pub mod flatsortedmap; pub mod gate; pub mod gatherarray; pub mod hex;