2.5 KiB · text History b85db9e
//! Device identity derived from the factory MAC address.
//!
//! The id is the lowercase hex of the last 3 bytes of the WiFi-STA MAC (6
//! chars, e.g. `0a1b2c`), so every unit is self-identifying without needing
//! anything flashed per-device.

/// Maximum SSID length in bytes (IEEE 802.11).
///
/// Worth enforcing: since esp-idf-svc 0.52 / heapless 0.9 the SSID goes into a
/// fixed-capacity string via `try_into()`, so an over-long name is a runtime
/// error at AP start rather than a silent truncation.
pub const SSID_MAX_LEN: usize = 32;

/// Format the 6-character lowercase-hex id from a MAC address.
pub fn format_id(mac: [u8; 6]) -> String {
    format!("{:02x}{:02x}{:02x}", mac[3], mac[4], mac[5])
}

/// SSID advertised while in provisioning mode, e.g. `setup_ssid("scan-setup",
/// "0a1b2c")` -> `scan-setup-0a1b2c`.
///
/// Truncated to [`SSID_MAX_LEN`] on a UTF-8 boundary if the prefix is long.
pub fn setup_ssid(prefix: &str, id: &str) -> String {
    let mut ssid = format!("{prefix}-{id}");
    if ssid.len() > SSID_MAX_LEN {
        let mut cut = SSID_MAX_LEN;
        while cut > 0 && !ssid.is_char_boundary(cut) {
            cut -= 1;
        }
        ssid.truncate(cut);
    }
    ssid
}

/// Read the factory WiFi-STA MAC and format it as a device id.
#[cfg(target_os = "espidf")]
pub fn device_id() -> String {
    use esp_idf_svc::sys::{esp_mac_type_t_ESP_MAC_WIFI_STA, esp_read_mac};

    let mut mac = [0u8; 6];
    // SAFETY: `esp_read_mac` writes exactly 6 bytes into the provided buffer.
    unsafe {
        esp_read_mac(mac.as_mut_ptr(), esp_mac_type_t_ESP_MAC_WIFI_STA);
    }
    format_id(mac)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn formats_last_three_bytes() {
        assert_eq!(format_id([0xde, 0xad, 0xbe, 0x0a, 0x1b, 0x2c]), "0a1b2c");
    }

    #[test]
    fn pads_single_hex_digits() {
        assert_eq!(format_id([0, 0, 0, 0x01, 0x02, 0x03]), "010203");
    }

    #[test]
    fn builds_setup_ssid() {
        assert_eq!(setup_ssid("scan-setup", "0a1b2c"), "scan-setup-0a1b2c");
    }

    #[test]
    fn truncates_over_long_ssid() {
        let ssid = setup_ssid(&"x".repeat(40), "0a1b2c");
        assert_eq!(ssid.len(), SSID_MAX_LEN);
    }

    #[test]
    fn truncation_respects_utf8_boundaries() {
        // 'é' is 2 bytes; cutting at 32 must not split it.
        let ssid = setup_ssid(&"é".repeat(20), "0a1b2c");
        assert!(ssid.len() <= SSID_MAX_LEN);
        assert!(std::str::from_utf8(ssid.as_bytes()).is_ok());
    }
}