//! ADR-118 / ADR-125 §2.1.d — Python binding for the BFLD `PrivacyClass` //! enum and the HAP-eligibility gate. //! //! Python: //! ```python //! from wifi_densepose import PrivacyClass, allows_hap, allows_matter, allows_network //! //! PrivacyClass.Anonymous # → 2 //! allows_hap(PrivacyClass.Raw) # → False (I1 invariant) //! allows_hap(PrivacyClass.Anonymous)# → True //! allows_matter(PrivacyClass.Restricted) # → True (ADR-122 §2.4) //! ``` //! //! This is the SOTA replacement for the Python port that ships in //! `scripts/c6-presence-watcher.py::PrivacyClass`. When the //! `wifi-densepose` PyPI wheel lands (ADR-117 P5), runtimes flip from //! the Python port to this Rust-backed binding and get the same enum //! semantics as every other consumer of the published //! `wifi-densepose-bfld 0.3.0` crate. use pyo3::prelude::*; use wifi_densepose_bfld::PrivacyClass; /// Python-facing wrapper for [`wifi_densepose_bfld::PrivacyClass`]. /// /// Repr matches the Rust enum byte values 0..=3. #[pyclass(eq, eq_int, hash, frozen, name = "PrivacyClass", module = "wifi_densepose")] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum PyPrivacyClass { Raw = 0, Derived = 1, Anonymous = 2, Restricted = 3, } impl From for PyPrivacyClass { fn from(c: PrivacyClass) -> Self { match c { PrivacyClass::Raw => Self::Raw, PrivacyClass::Derived => Self::Derived, PrivacyClass::Anonymous => Self::Anonymous, PrivacyClass::Restricted => Self::Restricted, } } } impl From for PrivacyClass { fn from(c: PyPrivacyClass) -> Self { match c { PyPrivacyClass::Raw => Self::Raw, PyPrivacyClass::Derived => Self::Derived, PyPrivacyClass::Anonymous => Self::Anonymous, PyPrivacyClass::Restricted => Self::Restricted, } } } #[pymethods] impl PyPrivacyClass { /// True if frames of this class may cross a `NetworkSink`. /// Class 0 (`Raw`) is local-only by structural invariant I1 /// (ADR-118 §2.2). #[getter] fn allows_network(&self) -> bool { PrivacyClass::from(*self).allows_network() } /// True if frames of this class may cross the Matter boundary. /// Only classes 2 (`Anonymous`) and 3 (`Restricted`) qualify per /// ADR-122 §2.4 / ADR-125 §2.1.d. #[getter] fn allows_matter(&self) -> bool { PrivacyClass::from(*self).allows_matter() } /// True if frames of this class may cross the HomeKit Accessory /// Protocol boundary. Same set as `allows_matter` — class 2 or 3. #[getter] fn allows_hap(&self) -> bool { // HAP eligibility is the same shape as Matter eligibility per // ADR-125 §2.1.d; we don't add a separate Rust method until // there's a divergence to justify it. PrivacyClass::from(*self).allows_matter() } /// Byte value (0..=3) for serialization. #[getter] fn as_u8(&self) -> u8 { PrivacyClass::from(*self).as_u8() } fn __repr__(&self) -> String { match self { Self::Raw => "PrivacyClass.Raw", Self::Derived => "PrivacyClass.Derived", Self::Anonymous => "PrivacyClass.Anonymous", Self::Restricted => "PrivacyClass.Restricted", } .to_string() } /// Map a byte value 0..=3 to the corresponding `PrivacyClass`. /// Raises `ValueError` on out-of-range input. #[staticmethod] fn from_u8(v: u8) -> PyResult { PrivacyClass::try_from(v) .map(Self::from) .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string())) } /// Map a string ("raw" / "derived" / "anonymous" / "restricted", /// case-insensitive) to the corresponding `PrivacyClass`. Raises /// `ValueError` on unknown names. #[staticmethod] fn from_str(s: &str) -> PyResult { match s.to_ascii_lowercase().as_str() { "raw" => Ok(Self::Raw), "derived" => Ok(Self::Derived), "anonymous" => Ok(Self::Anonymous), "restricted" => Ok(Self::Restricted), _ => Err(pyo3::exceptions::PyValueError::new_err(format!( "invalid PrivacyClass name: {s:?} (expected raw/derived/anonymous/restricted)" ))), } } } /// Free-function helper: `True` iff `c` may cross the HAP boundary. /// Convenience wrapper so Python callers can write /// `allows_hap(PrivacyClass.Anonymous)` without method-call syntax. #[pyfunction] fn allows_hap(c: PyPrivacyClass) -> bool { c.allows_hap() } /// Free-function helper: `True` iff `c` may cross a `NetworkSink`. #[pyfunction] fn allows_network(c: PyPrivacyClass) -> bool { c.allows_network() } /// Free-function helper: `True` iff `c` may cross the Matter boundary. #[pyfunction] fn allows_matter(c: PyPrivacyClass) -> bool { c.allows_matter() } pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_function(wrap_pyfunction!(allows_hap, m)?)?; m.add_function(wrap_pyfunction!(allows_network, m)?)?; m.add_function(wrap_pyfunction!(allows_matter, m)?)?; Ok(()) }