This crate contains SDN specific types, so they can be re-used across
multiple crates (The initial use-case being shared types between
proxmox-frr and proxmox-ve-config).

This initial commit contains types for the following entities:
* OpenFabric Hello Interval/Multiplier and CSNP Interval
* Network Entity Title (used as Router IDs in IS-IS / OpenFabric)
* OSPF Area

Co-authored-by: Gabriel Goller <g.gol...@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com>
---
 Cargo.toml                             |   9 +
 proxmox-sdn-types/Cargo.toml           |  19 ++
 proxmox-sdn-types/debian/changelog     |   5 +
 proxmox-sdn-types/debian/control       |  53 ++++
 proxmox-sdn-types/debian/copyright     |  18 ++
 proxmox-sdn-types/debian/debcargo.toml |   7 +
 proxmox-sdn-types/src/area.rs          |  50 ++++
 proxmox-sdn-types/src/lib.rs           |   3 +
 proxmox-sdn-types/src/net.rs           | 329 +++++++++++++++++++++++++
 proxmox-sdn-types/src/openfabric.rs    |  72 ++++++
 proxmox-ve-config/Cargo.toml           |   8 +-
 11 files changed, 569 insertions(+), 4 deletions(-)
 create mode 100644 proxmox-sdn-types/Cargo.toml
 create mode 100644 proxmox-sdn-types/debian/changelog
 create mode 100644 proxmox-sdn-types/debian/control
 create mode 100644 proxmox-sdn-types/debian/copyright
 create mode 100644 proxmox-sdn-types/debian/debcargo.toml
 create mode 100644 proxmox-sdn-types/src/area.rs
 create mode 100644 proxmox-sdn-types/src/lib.rs
 create mode 100644 proxmox-sdn-types/src/net.rs
 create mode 100644 proxmox-sdn-types/src/openfabric.rs

diff --git a/Cargo.toml b/Cargo.toml
index b6e6df7..4f5a6ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
 [workspace]
 members = [
     "proxmox-ve-config",
+    "proxmox-sdn-types",
 ]
 exclude = [
     "build",
@@ -16,4 +17,12 @@ exclude = [ "debian" ]
 rust-version = "1.82"
 
 [workspace.dependencies]
+anyhow = "1"
+const_format = "0.2"
+regex = "1.7"
+serde = { version = "1" }
+serde_with = "3"
+thiserror = "1.0.59"
+
 proxmox-network-types = { version = "0.1" }
+proxmox-schema = { version = "4" }
diff --git a/proxmox-sdn-types/Cargo.toml b/proxmox-sdn-types/Cargo.toml
new file mode 100644
index 0000000..09cd581
--- /dev/null
+++ b/proxmox-sdn-types/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "proxmox-sdn-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]
+anyhow = { workspace = true }
+const_format = { workspace = true }
+regex = { workspace = true }
+serde = { workspace = true, features = [ "derive" ] }
+serde_with = { workspace = true }
+
+proxmox-schema = { workspace = true, features = [ "api-macro", "api-types" ] }
+proxmox-serde = { version = "0.1.2", features = [ "perl" ] }
diff --git a/proxmox-sdn-types/debian/changelog 
b/proxmox-sdn-types/debian/changelog
new file mode 100644
index 0000000..422921c
--- /dev/null
+++ b/proxmox-sdn-types/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-sdn-types (0.1.0-1) unstable; urgency=medium
+
+  * Initial release.
+
+ -- Proxmox Support Team <supp...@proxmox.com>  Mon, 03 Jun 2024 10:51:11 +0200
diff --git a/proxmox-sdn-types/debian/control b/proxmox-sdn-types/debian/control
new file mode 100644
index 0000000..bfdb47e
--- /dev/null
+++ b/proxmox-sdn-types/debian/control
@@ -0,0 +1,53 @@
+Source: rust-proxmox-sdn-types
+Section: rust
+Priority: optional
+Build-Depends: debhelper-compat (= 13),
+ dh-sequence-cargo
+Build-Depends-Arch: cargo:native <!nocheck>,
+ rustc:native (>= 1.82) <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-anyhow-1+default-dev <!nocheck>,
+ librust-const-format-0.2+default-dev <!nocheck>,
+ librust-proxmox-schema-4+api-macro-dev <!nocheck>,
+ librust-proxmox-schema-4+api-types-dev <!nocheck>,
+ librust-proxmox-schema-4+default-dev <!nocheck>,
+ librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~) <!nocheck>,
+ librust-proxmox-serde-0.1+perl-dev (>= 0.1.2-~~) <!nocheck>,
+ librust-regex-1+default-dev (>= 1.7-~~) <!nocheck>,
+ librust-serde-1+default-dev <!nocheck>,
+ librust-serde-1+derive-dev <!nocheck>,
+ librust-serde-with-3+default-dev <!nocheck>
+Maintainer: Proxmox Support Team <supp...@proxmox.com>
+Standards-Version: 4.7.0
+Vcs-Git: git://git.proxmox.com/git/proxmox-ve-rs.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox-ve-rs.git
+Homepage: https://proxmox.com
+X-Cargo-Crate: proxmox-sdn-types
+Rules-Requires-Root: no
+
+Package: librust-proxmox-sdn-types-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-anyhow-1+default-dev,
+ librust-const-format-0.2+default-dev,
+ librust-proxmox-schema-4+api-macro-dev,
+ librust-proxmox-schema-4+api-types-dev,
+ librust-proxmox-schema-4+default-dev,
+ librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~),
+ librust-proxmox-serde-0.1+perl-dev (>= 0.1.2-~~),
+ librust-regex-1+default-dev (>= 1.7-~~),
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev,
+ librust-serde-with-3+default-dev
+Provides:
+ librust-proxmox-sdn-types+default-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0+default-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0.1-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-sdn-types-0.1.0+default-dev (= ${binary:Version})
+Description: Rust crate "proxmox-sdn-types" - Rust source code
+ Source code for Debianized Rust crate "proxmox-sdn-types"
diff --git a/proxmox-sdn-types/debian/copyright 
b/proxmox-sdn-types/debian/copyright
new file mode 100644
index 0000000..1ea8a56
--- /dev/null
+++ b/proxmox-sdn-types/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2019 - 2025 Proxmox Server Solutions GmbH <supp...@proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-sdn-types/debian/debcargo.toml 
b/proxmox-sdn-types/debian/debcargo.toml
new file mode 100644
index 0000000..87a787e
--- /dev/null
+++ b/proxmox-sdn-types/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <supp...@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox-ve-rs.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox-ve-rs.git";
diff --git a/proxmox-sdn-types/src/area.rs b/proxmox-sdn-types/src/area.rs
new file mode 100644
index 0000000..067e9f6
--- /dev/null
+++ b/proxmox-sdn-types/src/area.rs
@@ -0,0 +1,50 @@
+use std::{fmt::Display, net::Ipv4Addr};
+
+use anyhow::Error;
+use proxmox_schema::{ApiType, Schema, StringSchema, UpdaterType};
+use serde_with::{DeserializeFromStr, SerializeDisplay};
+
+/// An OSPF Area.
+///
+/// Internally the area is just a 32 bit number and is often represented in 
dotted-decimal
+/// notation, like an IPv4. FRR also allows us to specify it as a number or an 
IPv4-Address.
+/// To keep a nice user experience we keep whichever format the user entered.
+#[derive(
+    Debug, DeserializeFromStr, SerializeDisplay, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord,
+)]
+pub enum Area {
+    Number(u32),
+    IpAddress(Ipv4Addr),
+}
+
+impl ApiType for Area {
+    const API_SCHEMA: Schema =
+        StringSchema::new("The OSPF area, which can be a number or a 
ip-address.").schema();
+}
+
+impl UpdaterType for Area {
+    type Updater = Option<Area>;
+}
+
+impl std::str::FromStr for Area {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if let Ok(ip) = Ipv4Addr::from_str(s) {
+            Ok(Self::IpAddress(ip))
+        } else if let Ok(number) = u32::from_str(s) {
+            Ok(Self::Number(number))
+        } else {
+            anyhow::bail!("Area is not a number, nor an ip address");
+        }
+    }
+}
+
+impl Display for Area {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Area::Number(n) => write!(f, "{n}"),
+            Area::IpAddress(i) => write!(f, "{i}"),
+        }
+    }
+}
diff --git a/proxmox-sdn-types/src/lib.rs b/proxmox-sdn-types/src/lib.rs
new file mode 100644
index 0000000..f582d90
--- /dev/null
+++ b/proxmox-sdn-types/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod net;
+pub mod area;
+pub mod openfabric;
diff --git a/proxmox-sdn-types/src/net.rs b/proxmox-sdn-types/src/net.rs
new file mode 100644
index 0000000..78a4798
--- /dev/null
+++ b/proxmox-sdn-types/src/net.rs
@@ -0,0 +1,329 @@
+use std::{
+    fmt::Display,
+    net::{IpAddr, Ipv4Addr, Ipv6Addr},
+};
+
+use anyhow::{bail, Error};
+use const_format::concatcp;
+use serde::{Deserialize, Serialize};
+
+use proxmox_schema::{api, api_string_type, const_regex, ApiStringFormat, 
UpdaterType};
+
+const NET_AFI_REGEX_STR: &str = r"(?:[a-fA-F0-9]{2})";
+const NET_AREA_REGEX_STR: &str = r"(?:[a-fA-F0-9]{4})";
+const NET_SYSTEM_ID_REGEX_STR: &str = 
r"(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})\.(?:[a-fA-F0-9]{4})";
+const NET_SELECTOR_REGEX_STR: &str = r"(?:[a-fA-F0-9]{2})";
+
+const_regex! {
+    NET_AFI_REGEX = concatcp!(r"^", NET_AFI_REGEX_STR, r"$");
+    NET_AREA_REGEX = concatcp!(r"^", NET_AREA_REGEX_STR, r"$");
+    NET_SYSTEM_ID_REGEX = concatcp!(r"^", NET_SYSTEM_ID_REGEX_STR, r"$");
+    NET_SELECTOR_REGEX = concatcp!(r"^", NET_SELECTOR_REGEX_STR, r"$");
+}
+
+const NET_AFI_FORMAT: ApiStringFormat = 
ApiStringFormat::Pattern(&NET_AFI_REGEX);
+const NET_AREA_FORMAT: ApiStringFormat = 
ApiStringFormat::Pattern(&NET_AREA_REGEX);
+const NET_SYSTEM_ID_FORMAT: ApiStringFormat = 
ApiStringFormat::Pattern(&NET_SYSTEM_ID_REGEX);
+const NET_SELECTOR_FORMAT: ApiStringFormat = 
ApiStringFormat::Pattern(&NET_SELECTOR_REGEX);
+
+api_string_type! {
+    /// Address Family authority Identifier - 49 The AFI value 49 is what 
IS-IS (and openfabric) uses
+    /// for private addressing.
+    #[api(format: &NET_AFI_FORMAT)]
+    #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, 
Deserialize)]
+    struct NetAFI(String);
+}
+
+impl Default for NetAFI {
+    fn default() -> Self {
+        Self("49".to_owned())
+    }
+}
+
+impl UpdaterType for NetAFI {
+    type Updater = Option<NetAFI>;
+}
+
+api_string_type! {
+    /// 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.
+    #[api(format: &NET_AREA_FORMAT)]
+    #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+    struct NetArea(String);
+}
+
+impl Default for NetArea {
+    fn default() -> Self {
+        Self("0001".to_owned())
+    }
+}
+
+impl UpdaterType for NetArea {
+    type Updater = Option<NetArea>;
+}
+
+api_string_type! {
+    /// 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.
+    #[api(format: &NET_SYSTEM_ID_FORMAT)]
+    #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+    struct NetSystemId(String);
+}
+
+impl UpdaterType for NetSystemId {
+    type Updater = Option<NetSystemId>;
+}
+
+/// Convert IP-Address to a NET address with the default afi, area and 
selector values. Note that a
+/// valid Ipv4Addr is always a valid SystemId as well.
+impl From<Ipv4Addr> for NetSystemId {
+    fn from(value: Ipv4Addr) -> Self {
+        let octets = value.octets();
+
+        let system_id_str = format!(
+            "{:03}{:01}.{:02}{:02}.{:01}{:03}",
+            octets[0],
+            octets[1] / 100,
+            octets[1] % 100,
+            octets[2] / 10,
+            octets[2] % 10,
+            octets[3]
+        );
+
+        Self(system_id_str)
+    }
+}
+
+/// Convert IPv6-Address to a NET address with the default afi, area and 
selector values. Note that a
+/// valid Ipv6Addr is always a valid SystemId as well.
+impl From<Ipv6Addr> for NetSystemId {
+    fn from(value: Ipv6Addr) -> Self {
+        let segments = value.segments();
+
+        // Use the last 3 segments (out of 8) of the IPv6 address
+        let system_id_str = format!(
+            "{:04x}.{:04x}.{:04x}",
+            segments[5], segments[6], segments[7]
+        );
+
+        Self(system_id_str)
+    }
+}
+
+api_string_type! {
+    /// NET selector: 00 Must always be 00. This setting indicates “this 
system” or “local system.”
+    #[api(format: &NET_SELECTOR_FORMAT)]
+    #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+    struct NetSelector(String);
+}
+
+impl UpdaterType for NetSelector {
+    type Updater = Option<NetSelector>;
+}
+
+impl Default for NetSelector {
+    fn default() -> Self {
+        Self("00".to_owned())
+    }
+}
+
+/// The Network Entity Title (NET).
+///
+/// Every OpenFabric node is identified through the NET. It has a network and 
a host
+/// part.
+/// The first part is the network part (also called area). The entire 
OpenFabric fabric has to have
+/// the same network part (afi + area). The first number is the [`NetAFI`] and 
the second is the
+/// [`NetArea`].
+/// e.g.: "49.0001"
+/// The second part is the host part, which has to differ on every node in the 
fabric, but *not*
+/// between fabrics on the same node. It contains the [`NetSystemId`] and the 
[`NetSelector`].
+/// e.g.: "1921.6800.1002.00"
+#[api]
+#[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq, 
PartialOrd, Ord)]
+pub struct Net {
+    afi: NetAFI,
+    area: NetArea,
+    system: NetSystemId,
+    selector: NetSelector,
+}
+
+impl UpdaterType for Net {
+    type Updater = Option<Net>;
+}
+
+impl std::str::FromStr for Net {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let parts: Vec<&str> = s.split(".").collect();
+
+        if parts.len() != 6 {
+            bail!("invalid NET format: {s}")
+        }
+
+        let system = format!("{}.{}.{}", parts[2], parts[3], parts[4],);
+
+        Ok(Self {
+            afi: NetAFI::from_string(parts[0].to_string())?,
+            area: NetArea::from_string(parts[1].to_string())?,
+            system: NetSystemId::from_string(system.to_string())?,
+            selector: NetSelector::from_string(parts[5].to_string())?,
+        })
+    }
+}
+
+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
+        )
+    }
+}
+
+/// Default NET address for a given Ipv4Addr. This adds the default afi, area 
and selector to the
+/// address.
+impl From<Ipv4Addr> for Net {
+    fn from(value: Ipv4Addr) -> Self {
+        Self {
+            afi: NetAFI::default(),
+            area: NetArea::default(),
+            system: value.into(),
+            selector: NetSelector::default(),
+        }
+    }
+}
+
+/// Default NET address for a given Ipv6Addr. This adds the default afi, area 
and selector to the
+/// address.
+impl From<Ipv6Addr> for Net {
+    fn from(value: Ipv6Addr) -> Self {
+        Self {
+            afi: NetAFI::default(),
+            area: NetArea::default(),
+            system: value.into(),
+            selector: NetSelector::default(),
+        }
+    }
+}
+
+/// Default NET address for a given IpAddr (can be either Ipv4 or Ipv6). This 
adds the default afi,
+/// area and selector to the address.
+impl From<IpAddr> for Net {
+    fn from(value: IpAddr) -> Self {
+        match value {
+            IpAddr::V4(ipv4_addr) => ipv4_addr.into(),
+            IpAddr::V6(ipv6_addr) => ipv6_addr.into(),
+        }
+    }
+}
+
+#[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.ba1f.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.ba1f".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";
+        input.parse::<Net>().expect_err("invalid NET selector");
+
+        let input = "49.0001.1921.6800.1002.00.00";
+        input
+            .parse::<Net>()
+            .expect_err("invalid amount of elements");
+
+        let input = "49.0001.1921.6800.10002.00";
+        input.parse::<Net>().expect_err("invalid system id");
+
+        let input = "49.0001.1921.6800.1z02.00";
+        input.parse::<Net>().expect_err("invalid system id");
+
+        let input = "409.0001.1921.6800.1002.00";
+        input.parse::<Net>().expect_err("invalid AFI");
+
+        let input = "49.00001.1921.6800.1002.00";
+        input.parse::<Net>().expect_err("invalid area");
+    }
+
+    #[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");
+    }
+
+    #[test]
+    fn test_net_from_ipv4() {
+        let ip: Ipv4Addr = "192.168.1.100".parse().unwrap();
+        let net: Net = ip.into();
+        assert_eq!(format!("{net}"), "49.0001.1921.6800.1100.00");
+
+        let ip1: Ipv4Addr = "10.10.2.245".parse().unwrap();
+        let net1: Net = ip1.into();
+        assert_eq!(format!("{net1}"), "49.0001.0100.1000.2245.00");
+
+        let ip2: Ipv4Addr = "1.1.1.1".parse().unwrap();
+        let net2: Net = ip2.into();
+        assert_eq!(format!("{net2}"), "49.0001.0010.0100.1001.00");
+    }
+
+    #[test]
+    fn test_net_from_ipv6() {
+        // 2001:db8::1 -> [2001, 0db8, 0, 0, 0, 0, 0, 1]
+        // last 3 segments: [0, 0, 1]
+        let ip: Ipv6Addr = "2001:db8::1".parse().unwrap();
+        let net: Net = ip.into();
+        assert_eq!(format!("{net}"), "49.0001.0000.0000.0001.00");
+
+        // fe80::1234:5678:abcd -> [fe80, 0, 0, 0, 0, 1234, 5678, abcd]
+        // last 3 segments: [1234, 5678, abcd]
+        let ip1: Ipv6Addr = "fe80::1234:5678:abcd".parse().unwrap();
+        let net1: Net = ip1.into();
+        assert_eq!(format!("{net1}"), "49.0001.1234.5678.abcd.00");
+
+        // 2001:0db8:85a3::8a2e:370:7334 -> [2001, 0db8, 85a3, 0, 0, 8a2e, 
0370, 7334]
+        // last 3 segments: [8a2e, 0370, 7334]
+        let ip2: Ipv6Addr = "2001:0db8:85a3::8a2e:370:7334".parse().unwrap();
+        let net2: Net = ip2.into();
+        assert_eq!(format!("{net2}"), "49.0001.8a2e.0370.7334.00");
+
+        // ::1 -> [0, 0, 0, 0, 0, 0, 0, 1]
+        // last 3 segments: [0, 0, 1]
+        let ip3: Ipv6Addr = "::1".parse().unwrap();
+        let net3: Net = ip3.into();
+        assert_eq!(format!("{net3}"), "49.0001.0000.0000.0001.00");
+
+        // a:b::0 -> [a, b, 0, 0, 0, 0, 0, 0]
+        // last 3 segments: [0, 0, 0]
+        let ip4: Ipv6Addr = "a:b::0".parse().unwrap();
+        let net4: Net = ip4.into();
+        assert_eq!(format!("{net4}"), "49.0001.0000.0000.0000.00");
+    }
+}
diff --git a/proxmox-sdn-types/src/openfabric.rs 
b/proxmox-sdn-types/src/openfabric.rs
new file mode 100644
index 0000000..c79e2d9
--- /dev/null
+++ b/proxmox-sdn-types/src/openfabric.rs
@@ -0,0 +1,72 @@
+use serde::{Deserialize, Serialize};
+use std::fmt::Display;
+
+use proxmox_schema::{api, UpdaterType};
+
+/// The OpenFabric CSNP Interval.
+///
+/// The Complete Sequence Number Packets (CSNP) interval in seconds. The 
interval range is 1 to
+/// 600.
+#[api(
+    type: Integer,
+    minimum: 1,
+    maximum: 600,
+)]
+#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, 
PartialOrd, Ord)]
+#[serde(transparent)]
+pub struct CsnpInterval(#[serde(deserialize_with = 
"proxmox_serde::perl::deserialize_u16")] u16);
+
+impl UpdaterType for CsnpInterval {
+    type Updater = Option<CsnpInterval>;
+}
+
+impl Display for CsnpInterval {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+/// The OpenFabric Hello Interval.
+///
+/// The Hello Interval for a given interface in seconds. The range is 1 to 
600. Hello packets are
+/// used to establish and maintain adjacency between OpenFabric neighbors.
+#[api(
+    type: Integer,
+    minimum: 1,
+    maximum: 600,
+)]
+#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, 
PartialOrd, Ord)]
+#[serde(transparent)]
+pub struct HelloInterval(#[serde(deserialize_with = 
"proxmox_serde::perl::deserialize_u16")] u16);
+
+impl UpdaterType for HelloInterval {
+    type Updater = Option<HelloInterval>;
+}
+
+impl Display for HelloInterval {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+/// The OpenFabric Hello Multiplier.
+///
+/// This is the multiplier for the hello holding time on a given interface. 
The range is 2 to 100.
+#[api(
+    type: Integer,
+    minimum: 2,
+    maximum: 100,
+)]
+#[derive(Serialize, Deserialize, Hash, Debug, Clone, Copy, PartialEq, Eq, 
PartialOrd, Ord)]
+#[serde(transparent)]
+pub struct HelloMultiplier(#[serde(deserialize_with = 
"proxmox_serde::perl::deserialize_u16")] u16);
+
+impl UpdaterType for HelloMultiplier {
+    type Updater = Option<HelloMultiplier>;
+}
+
+impl Display for HelloMultiplier {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml
index 72fb627..c240a87 100644
--- a/proxmox-ve-config/Cargo.toml
+++ b/proxmox-ve-config/Cargo.toml
@@ -8,14 +8,14 @@ exclude.workspace = true
 
 [dependencies]
 log = "0.4"
-anyhow = "1"
+anyhow = { workspace = true }
 nix = "0.26"
-thiserror = "1.0.59"
+thiserror = { workspace = true }
 
-serde = { version = "1", features = [ "derive" ] }
+serde = { workspace = true, features = [ "derive" ] }
 serde_json = "1"
 serde_plain = "1"
-serde_with = "3"
+serde_with = { workspace = true }
 proxmox-serde = { version = "0.1.2", features = [ "perl" ]}
 
 proxmox-network-types = { workspace = true }
-- 
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