The new proxmox-network-types crate holds some common types that are
used by proxmox-frr, proxmox-ve-config and proxmox-perl-rs. These types
are here because we don't want proxmox-frr to be a dependency of
proxmox-ve-config or vice-versa (or at least it should be feature-gated).
They should be independent.

Signed-off-by: Gabriel Goller <g.gol...@proxmox.com>
---
 Cargo.toml                       |   6 +
 proxmox-network-types/Cargo.toml |  15 ++
 proxmox-network-types/src/lib.rs |   1 +
 proxmox-network-types/src/net.rs | 239 +++++++++++++++++++++++++++++++
 4 files changed, 261 insertions(+)
 create mode 100644 proxmox-network-types/Cargo.toml
 create mode 100644 proxmox-network-types/src/lib.rs
 create mode 100644 proxmox-network-types/src/net.rs

diff --git a/Cargo.toml b/Cargo.toml
index dc7f312fb8a9..e452c931e78c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 members = [
     "proxmox-ve-config",
+    "proxmox-network-types",
 ]
 exclude = [
     "build",
@@ -15,3 +16,8 @@ homepage = "https://proxmox.com";
 exclude = [ "debian" ]
 rust-version = "1.82"
 
+[workspace.dependencies]
+proxmox-section-config = "2.1.1"
+serde = "1"
+serde_with = "3.8.1"
+thiserror = "1.0.59"
diff --git a/proxmox-network-types/Cargo.toml b/proxmox-network-types/Cargo.toml
new file mode 100644
index 000000000000..93f4df87a59f
--- /dev/null
+++ b/proxmox-network-types/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "proxmox-network-types"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+exclude.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+thiserror = { workspace = true }
+anyhow = "1"
+serde = { workspace = true, features = [ "derive" ] }
+serde_with = { workspace = true }
diff --git a/proxmox-network-types/src/lib.rs b/proxmox-network-types/src/lib.rs
new file mode 100644
index 000000000000..f9faf2ff6542
--- /dev/null
+++ b/proxmox-network-types/src/lib.rs
@@ -0,0 +1 @@
+pub mod net;
diff --git a/proxmox-network-types/src/net.rs b/proxmox-network-types/src/net.rs
new file mode 100644
index 000000000000..5fdbe3920800
--- /dev/null
+++ b/proxmox-network-types/src/net.rs
@@ -0,0 +1,239 @@
+use std::{fmt::Display, str::FromStr};
+
+use serde::Serialize;
+use serde_with::{DeserializeFromStr, SerializeDisplay};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum NetError {
+    #[error("Some octets are missing")]
+    WrongLength,
+    #[error("The NET selector must be two characters wide and be 00")]
+    InvalidNetSelector,
+    #[error("Invalid AFI (wrong size or position)")]
+    InvalidAFI,
+    #[error("Invalid Area (wrong size or position)")]
+    InvalidArea,
+    #[error("Invalid SystemId (wrong size or position)")]
+    InvalidSystemId,
+}
+
+/// Address Family authority Identifier - 49 The AFI value 49 is what IS-IS 
(and openfabric) uses
+/// for private addressing.
+#[derive(
+    Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord,
+)]
+struct NetAFI(String);
+
+impl Default for NetAFI {
+    fn default() -> Self {
+        Self("49".to_owned())
+    }
+}
+
+impl Display for NetAFI {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl FromStr for NetAFI {
+    type Err = NetError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.len() != 2 {
+            Err(NetError::InvalidAFI)
+        } else {
+            Ok(Self(s.to_owned()))
+        }
+    }
+}
+
+/// Area identifier: 0001 IS-IS area number (numerical area 1)
+/// The second part (system) of the `net` identifier. Every node has to have a 
different system
+/// number.
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+struct NetArea(String);
+
+impl Default for NetArea {
+    fn default() -> Self {
+        Self("0001".to_owned())
+    }
+}
+
+impl Display for NetArea {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl FromStr for NetArea {
+    type Err = NetError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.len() != 4 {
+            Err(NetError::InvalidArea)
+        } else {
+            Ok(Self(s.to_owned()))
+        }
+    }
+}
+
+/// System identifier: 1921.6800.1002 - for system identifiers we recommend to 
use IP address or
+/// MAC address of the router itself. The way to construct this is to keep all 
of the zeroes of the
+/// router IP address, and then change the periods from being every three 
numbers to every four
+/// numbers. The address that is listed here is 192.168.1.2, which if expanded 
will turn into
+/// 192.168.001.002. Then all one has to do is move the dots to have four 
numbers instead of three.
+/// This gives us 1921.6800.1002.
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+struct NetSystemId(String);
+
+impl Display for NetSystemId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl FromStr for NetSystemId {
+    type Err = NetError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.split(".").count() != 3 || s.split(".").any(|octet| octet.len() 
!= 4) {
+            Err(NetError::InvalidArea)
+        } else {
+            Ok(Self(s.to_owned()))
+        }
+    }
+}
+
+/// NET selector: 00 Must always be 00. This setting indicates “this system” 
or “local system.”
+#[derive(Debug, DeserializeFromStr, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+struct NetSelector(String);
+
+impl Default for NetSelector {
+    fn default() -> Self {
+        Self("00".to_owned())
+    }
+}
+
+impl Display for NetSelector {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl FromStr for NetSelector {
+    type Err = NetError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.len() != 2 {
+            Err(NetError::InvalidNetSelector)
+        } else {
+            Ok(Self(s.to_owned()))
+        }
+    }
+}
+
+/// The first part (area) of the `net` identifier. The entire OpenFabric 
fabric has to have the
+/// same area.
+/// f.e.: "49.0001"
+#[derive(
+    Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord,
+)]
+pub struct Net {
+    afi: NetAFI,
+    area: NetArea,
+    system: NetSystemId,
+    selector: NetSelector,
+}
+
+impl FromStr for Net {
+    type Err = NetError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.split(".").count() != 6 {
+            return Err(NetError::WrongLength);
+        }
+        let mut iter = s.split(".");
+        let afi = iter.next().ok_or(NetError::WrongLength)?;
+        let area = iter.next().ok_or(NetError::WrongLength)?;
+        let system = format!(
+            "{}.{}.{}",
+            iter.next().ok_or(NetError::WrongLength)?,
+            iter.next().ok_or(NetError::WrongLength)?,
+            iter.next().ok_or(NetError::WrongLength)?
+        );
+        let selector = iter.next().ok_or(NetError::WrongLength)?;
+        Ok(Self {
+            afi: afi.parse()?,
+            area: area.parse()?,
+            system: system.parse()?,
+            selector: selector.parse()?,
+        })
+    }
+}
+
+impl Display for Net {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}.{}.{}.{}",
+            self.afi, self.area, self.system, self.selector
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_net_from_str() {
+        let input = "49.0001.1921.6800.1002.00";
+        let net = input.parse::<Net>().expect("this net should parse");
+        assert_eq!(net.afi, NetAFI("49".to_owned()));
+        assert_eq!(net.area, NetArea("0001".to_owned()));
+        assert_eq!(net.system, NetSystemId("1921.6800.1002".to_owned()));
+        assert_eq!(net.selector, NetSelector("00".to_owned()));
+
+        let input = "45.0200.0100.1001.0010.01";
+        let net = input.parse::<Net>().expect("this net should parse");
+        assert_eq!(net.afi, NetAFI("45".to_owned()));
+        assert_eq!(net.area, NetArea("0200".to_owned()));
+        assert_eq!(net.system, NetSystemId("0100.1001.0010".to_owned()));
+        assert_eq!(net.selector, NetSelector("01".to_owned()));
+    }
+
+    #[test]
+    fn test_net_from_str_failed() {
+        let input = "49.0001.1921.6800.1002.000";
+        assert!(matches!(
+            input.parse::<Net>(),
+            Err(NetError::InvalidNetSelector)
+        ));
+
+        let input = "49.0001.1921.6800.1002.00.00";
+        assert!(matches!(input.parse::<Net>(), Err(NetError::WrongLength)));
+
+        let input = "49.0001.1921.6800.10002.00";
+        assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidArea)));
+
+        let input = "409.0001.1921.6800.1002.00";
+        assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidAFI)));
+
+        let input = "49.00001.1921.6800.1002.00";
+        assert!(matches!(input.parse::<Net>(), Err(NetError::InvalidArea)));
+    }
+
+    #[test]
+    fn test_net_display() {
+        let net = Net {
+            afi: NetAFI("49".to_owned()),
+            area: NetArea("0001".to_owned()),
+            system: NetSystemId("1921.6800.1002".to_owned()),
+            selector: NetSelector("00".to_owned()),
+        };
+        assert_eq!(format!("{net}"), "49.0001.1921.6800.1002.00");
+    }
+}
+
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to