155 lines
5.1 KiB
Rust
155 lines
5.1 KiB
Rust
//! 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<PrivacyClass> 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<PyPrivacyClass> 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<Self> {
|
|
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<Self> {
|
|
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::<PyPrivacyClass>()?;
|
|
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(())
|
|
}
|