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