use std::collections::VecDeque;
use std::sync::Arc;
use axum::{
extract::{
ws::{Message, WebSocket, WebSocketUpgrade},
State,
},
response::{IntoResponse, Json},
};
use tokio::sync::{broadcast, RwLock};
pub use wifi_densepose_rufield::{
network_egress_allowed, snapshot_to_field_event, FieldEvent, RuViewPrivacyClass,
SensingClass, SensingFeatures, SensingSnapshot, Signer, SignalField,
};
pub const FIELD_RING_CAPACITY: usize = 64;
pub const FIELD_BROADCAST_CAPACITY: usize = 256;
pub const SIGNING_SEED_ENV: &str = "WDP_RUFIELD_SIGNING_SEED";
const DEV_SIGNING_SEED: &[u8; 32] = b"adr262-ruview-rufield-dev-seed!!";
pub struct FieldSurface {
signer: Signer,
recent: VecDeque<FieldEvent>,
tx: broadcast::Sender<String>,
using_dev_key: bool,
}
impl FieldSurface {
#[must_use]
pub fn from_seed(seed: &[u8; 32], using_dev_key: bool) -> Self {
let (tx, _rx) = broadcast::channel(FIELD_BROADCAST_CAPACITY);
Self {
signer: Signer::from_seed(seed),
recent: VecDeque::with_capacity(FIELD_RING_CAPACITY),
tx,
using_dev_key,
}
}
#[must_use]
pub fn from_env() -> Self {
match std::env::var(SIGNING_SEED_ENV).ok().and_then(|v| parse_seed(&v)) {
Some(seed) => {
tracing::info!(
"ADR-262 P3: RuField surface using signing seed from {SIGNING_SEED_ENV} \
(dedicated sensing key)"
);
Self::from_seed(&seed, false)
}
None => {
tracing::warn!(
"ADR-262 P3: {SIGNING_SEED_ENV} unset/invalid — RuField surface using the \
DETERMINISTIC DEV signing key. This is a dev/sensing key pending the \
ADR-262 §8 Q1 (P2) key-ownership decision; set {SIGNING_SEED_ENV} (64-hex \
or 32-byte value) for a real deployment."
);
Self::from_seed(DEV_SIGNING_SEED, true)
}
}
}
#[must_use]
pub fn signer_pubkey_hex(&self) -> String {
self.signer.public_hex()
}
#[must_use]
pub fn using_dev_key(&self) -> bool {
self.using_dev_key
}
#[must_use]
pub fn subscribe(&self) -> broadcast::Receiver<String> {
self.tx.subscribe()
}
#[must_use]
pub fn recent(&self) -> Vec<FieldEvent> {
self.recent.iter().cloned().collect()
}
pub fn emit(&mut self, snap: &SensingSnapshot) -> Option<FieldEvent> {
if !snap.classification.presence {
return None;
}
let event = snapshot_to_field_event(snap, &self.signer);
if !network_egress_allowed(event.observation.privacy_class, snap.identity_bound) {
tracing::trace!(
privacy_class = ?event.observation.privacy_class,
"ADR-262 P3: cycle held edge-local (above network policy), not surfaced on /api/field"
);
return None;
}
if self.recent.len() == FIELD_RING_CAPACITY {
self.recent.pop_front();
}
self.recent.push_back(event.clone());
if let Ok(json) = serde_json::to_string(&event) {
let _ = self.tx.send(json);
}
Some(event)
}
}
fn parse_seed(v: &str) -> Option<[u8; 32]> {
let v = v.trim();
if v.len() == 64 && v.bytes().all(|b| b.is_ascii_hexdigit()) {
let mut out = [0u8; 32];
for (i, chunk) in v.as_bytes().chunks(2).enumerate() {
let hi = (chunk[0] as char).to_digit(16)?;
let lo = (chunk[1] as char).to_digit(16)?;
out[i] = ((hi << 4) | lo) as u8;
}
return Some(out);
}
let bytes = v.as_bytes();
if bytes.len() >= 32 {
let mut out = [0u8; 32];
out.copy_from_slice(&bytes[..32]);
return Some(out);
}
None
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn build_snapshot(
timestamp_ns: u64,
node_id: String,
features: SensingFeatures,
classification: SensingClass,
signal_field: Option<SignalField>,
trust_class: RuViewPrivacyClass,
demoted: bool,
identity_bound: bool,
) -> SensingSnapshot {
SensingSnapshot {
timestamp_ns,
features,
classification,
signal_field,
trust_class,
demoted,
identity_bound,
node_id,
}
}
#[must_use]
pub fn ruview_class_from_bfld(class: wifi_densepose_bfld::PrivacyClass) -> RuViewPrivacyClass {
use wifi_densepose_bfld::PrivacyClass as B;
match class {
B::Raw => RuViewPrivacyClass::Raw,
B::Derived => RuViewPrivacyClass::Derived,
B::Anonymous => RuViewPrivacyClass::Anonymous,
B::Restricted => RuViewPrivacyClass::Restricted,
}
}
pub type FieldState = Arc<RwLock<FieldSurface>>;
pub async fn api_field(State(state): State<FieldState>) -> Json<serde_json::Value> {
let s = state.read().await;
Json(serde_json::json!({
"spec": "rufield",
"endpoint": "/api/field",
"signer_pubkey_hex": s.signer_pubkey_hex(),
"dev_signing_key": s.using_dev_key(),
"events": s.recent(),
}))
}
pub async fn ws_field(ws: WebSocketUpgrade, State(state): State<FieldState>) -> impl IntoResponse {
let rx = {
let s = state.read().await;
s.subscribe()
};
ws.on_upgrade(move |socket| handle_ws_field_client(socket, rx))
}
async fn handle_ws_field_client(mut socket: WebSocket, mut rx: broadcast::Receiver<String>) {
loop {
match rx.recv().await {
Ok(json) => {
if socket.send(Message::Text(json)).await.is_err() {
break; }
}
Err(broadcast::error::RecvError::Lagged(_)) => {
continue;
}
Err(broadcast::error::RecvError::Closed) => break,
}
}
}
#[must_use]
pub fn router(state: FieldState) -> axum::Router {
use axum::routing::get;
axum::Router::new()
.route("/api/field", get(api_field))
.route("/ws/field", get(ws_field))
.with_state(state)
}
#[cfg(test)]
mod tests {
use super::*;
use wifi_densepose_rufield::{is_fusable, PrivacyClass};
fn features() -> SensingFeatures {
SensingFeatures {
mean_rssi: -55.0,
variance: 0.4,
motion_band_power: 2.0,
breathing_band_power: 0.3,
dominant_freq_hz: 0.25,
change_points: 1,
spectral_power: 3.0,
}
}
fn present_class() -> SensingClass {
SensingClass {
motion_level: "low".into(),
presence: true,
confidence: 0.82,
}
}
#[test]
fn parse_seed_hex_and_raw_and_short() {
let hex = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff";
let parsed = parse_seed(hex).expect("valid hex seed");
assert_eq!(parsed[0], 0x00);
assert_eq!(parsed[31], 0xff);
assert!(parse_seed("0123456789abcdef0123456789abcdef").is_some());
assert!(parse_seed("short").is_none());
}
#[test]
fn anonymous_cycle_surfaces_fusable_event() {
let mut surface = FieldSurface::from_seed(DEV_SIGNING_SEED, true);
let snap = build_snapshot(
1_791_986_400_000_000_000,
"esp32_room_01".into(),
features(),
present_class(),
None,
RuViewPrivacyClass::Anonymous, false,
false,
);
let ev = surface.emit(&snap).expect("anonymous P2 cycle is surfaced");
assert_eq!(ev.observation.privacy_class, PrivacyClass::P2);
assert!(is_fusable(&ev), "live event must be ed25519-signed & fusable");
assert_eq!(surface.recent().len(), 1);
}
#[test]
fn derived_cycle_never_surfaces_low_privacy() {
let mut surface = FieldSurface::from_seed(DEV_SIGNING_SEED, true);
for identity_bound in [false, true] {
let snap = build_snapshot(
1_791_986_400_000_000_000,
"esp32_room_01".into(),
features(),
present_class(),
None,
RuViewPrivacyClass::Derived,
false,
identity_bound,
);
assert!(
surface.emit(&snap).is_none(),
"Derived cycle (identity_bound={identity_bound}) must be held edge-local"
);
}
assert!(surface.recent().is_empty(), "no Derived event may reach the surface");
}
#[test]
fn ring_buffer_is_bounded() {
let mut surface = FieldSurface::from_seed(DEV_SIGNING_SEED, true);
for i in 0..(FIELD_RING_CAPACITY + 10) {
let snap = build_snapshot(
1_791_986_400_000_000_000 + i as u64,
"esp32_room_01".into(),
features(),
present_class(),
None,
RuViewPrivacyClass::Anonymous,
false,
false,
);
surface.emit(&snap);
}
assert_eq!(surface.recent().len(), FIELD_RING_CAPACITY);
}
}