From 2cedd6c87c6e661e89d82f6434c308fe11f72166 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Mon, 23 Feb 2026 21:19:59 -0500 Subject: [PATCH 1/2] feat(#260): new `WifiMode` and `mode` to `WifiConnectionBuilder` --- nmrs/src/api/builders/mod.rs | 2 +- nmrs/src/api/builders/wifi.rs | 4 + nmrs/src/api/builders/wifi_builder.rs | 118 +++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/nmrs/src/api/builders/mod.rs b/nmrs/src/api/builders/mod.rs index 6588c4ef..d40aad4b 100644 --- a/nmrs/src/api/builders/mod.rs +++ b/nmrs/src/api/builders/mod.rs @@ -77,7 +77,7 @@ pub mod wireguard_builder; // Re-export core builder types pub use connection_builder::{ConnectionBuilder, IpConfig, Route}; -pub use wifi_builder::{WifiBand, WifiConnectionBuilder}; +pub use wifi_builder::{WifiBand, WifiConnectionBuilder, WifiMode}; pub use wireguard_builder::WireGuardBuilder; // Re-export builder functions for convenience diff --git a/nmrs/src/api/builders/wifi.rs b/nmrs/src/api/builders/wifi.rs index 0734e8d8..9e664cad 100644 --- a/nmrs/src/api/builders/wifi.rs +++ b/nmrs/src/api/builders/wifi.rs @@ -51,6 +51,10 @@ use crate::api::models::{self, ConnectionOptions}; /// /// # Note /// +/// This function always creates an infrastructure-mode connection. For access +/// point (hotspot) or ad-hoc connections, use [`WifiConnectionBuilder`] directly +/// with [`WifiMode`](super::wifi_builder::WifiMode). +/// /// This function is maintained for backward compatibility. For new code, /// consider using `WifiConnectionBuilder` for a more ergonomic API. pub fn build_wifi_connection( diff --git a/nmrs/src/api/builders/wifi_builder.rs b/nmrs/src/api/builders/wifi_builder.rs index 14327c14..4bb9d2f5 100644 --- a/nmrs/src/api/builders/wifi_builder.rs +++ b/nmrs/src/api/builders/wifi_builder.rs @@ -18,6 +18,35 @@ pub enum WifiBand { A, } +/// WiFi operating mode. +/// +/// Determines whether the device acts as a client connecting to an existing +/// network or creates its own network for other devices to join. +#[non_exhaustive] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum WifiMode { + /// Standard client mode — connects to an existing access point. + #[default] + Infrastructure, + /// Access point mode — the device acts as a WiFi hotspot. + /// + /// Typically paired with `.ipv4_shared()` so NetworkManager sets up + /// DHCP and NAT for connected clients. + Ap, + /// Ad-hoc (IBSS) mode — peer-to-peer networking without an access point. + Adhoc, +} + +impl WifiMode { + fn as_nm_str(self) -> &'static str { + match self { + Self::Infrastructure => "infrastructure", + Self::Ap => "ap", + Self::Adhoc => "adhoc", + } + } +} + /// Builder for WiFi (802.11) connections. /// /// This builder provides a type-safe, ergonomic API for creating WiFi connection @@ -65,9 +94,23 @@ pub enum WifiBand { /// .autoconnect(false) /// .build(); /// ``` +/// +/// ## Access Point (Hotspot) +/// +/// ```rust +/// use nmrs::builders::{WifiConnectionBuilder, WifiMode}; +/// +/// let settings = WifiConnectionBuilder::new("MyHotspot") +/// .mode(WifiMode::Ap) +/// .wpa_psk("hotspot_password") +/// .ipv4_shared() +/// .ipv6_ignore() +/// .build(); +/// ``` pub struct WifiConnectionBuilder { inner: ConnectionBuilder, ssid: String, + mode: WifiMode, security_configured: bool, hidden: Option, band: Option, @@ -86,6 +129,7 @@ impl WifiConnectionBuilder { Self { inner, ssid, + mode: WifiMode::default(), security_configured: false, hidden: None, band: None, @@ -194,6 +238,27 @@ impl WifiConnectionBuilder { self } + /// Sets the WiFi operating mode. + /// + /// Defaults to [`WifiMode::Infrastructure`] (standard client mode). + /// + /// # Example: Access Point + /// + /// ```rust + /// use nmrs::builders::{WifiConnectionBuilder, WifiMode}; + /// + /// let settings = WifiConnectionBuilder::new("MyHotspot") + /// .mode(WifiMode::Ap) + /// .wpa_psk("hotspot_password") + /// .ipv4_shared() + /// .ipv6_ignore() + /// .build(); + /// ``` + pub fn mode(mut self, mode: WifiMode) -> Self { + self.mode = mode; + self + } + // Delegation methods to inner ConnectionBuilder /// Applies connection options (autoconnect settings). @@ -226,6 +291,15 @@ impl WifiConnectionBuilder { self } + /// Configures IPv4 for internet connection sharing (DHCP + NAT). + /// + /// This is the typical IPv4 setting for [`WifiMode::Ap`] connections, + /// where the device provides network access to connected clients. + pub fn ipv4_shared(mut self) -> Self { + self.inner = self.inner.ipv4_shared(); + self + } + /// Configures IPv6 to use SLAAC/DHCPv6. pub fn ipv6_auto(mut self) -> Self { self.inner = self.inner.ipv6_auto(); @@ -246,7 +320,7 @@ impl WifiConnectionBuilder { // Build the 802-11-wireless section let mut wireless = HashMap::new(); wireless.insert("ssid", Value::from(self.ssid.as_bytes().to_vec())); - wireless.insert("mode", Value::from("infrastructure")); + wireless.insert("mode", Value::from(self.mode.as_nm_str())); // Add optional WiFi settings if let Some(hidden) = self.hidden { @@ -409,6 +483,48 @@ mod tests { ); } + #[test] + fn defaults_to_infrastructure_mode() { + let settings = WifiConnectionBuilder::new("DefaultMode") + .open() + .ipv4_auto() + .build(); + + let wireless = settings.get("802-11-wireless").unwrap(); + assert_eq!(wireless.get("mode"), Some(&Value::from("infrastructure"))); + } + + #[test] + fn builds_ap_mode_hotspot() { + let settings = WifiConnectionBuilder::new("MyHotspot") + .mode(WifiMode::Ap) + .wpa_psk("hotspot_pass") + .ipv4_shared() + .ipv6_ignore() + .build(); + + let wireless = settings.get("802-11-wireless").unwrap(); + assert_eq!(wireless.get("mode"), Some(&Value::from("ap"))); + + let ipv4 = settings.get("ipv4").unwrap(); + assert_eq!(ipv4.get("method"), Some(&Value::from("shared"))); + + let ipv6 = settings.get("ipv6").unwrap(); + assert_eq!(ipv6.get("method"), Some(&Value::from("ignore"))); + } + + #[test] + fn builds_adhoc_mode() { + let settings = WifiConnectionBuilder::new("PeerNet") + .mode(WifiMode::Adhoc) + .open() + .ipv4_auto() + .build(); + + let wireless = settings.get("802-11-wireless").unwrap(); + assert_eq!(wireless.get("mode"), Some(&Value::from("adhoc"))); + } + #[test] fn applies_connection_options() { let opts = ConnectionOptions { From b03959cbfd19c5dc662eb1f768d80f49012d2379 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Feb 2026 02:23:39 +0000 Subject: [PATCH 2/2] chore(nix): fix cargoHash --- package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nix b/package.nix index cbacb91e..4b0071bb 100644 --- a/package.nix +++ b/package.nix @@ -19,7 +19,7 @@ rustPlatform.buildRustPackage { src = ./.; - cargoHash = "sha256-FXCwFB7n75ZJUbEB/U0VkgHc9x811EZN+4/RHHONsbU="; + cargoHash = "sha256-la0SAZqDWfDIFohIkX8XscRqYrR3qHyVpa47uf6aqEI="; nativeBuildInputs = [ pkg-config