101 lines
4.2 KiB
Rust
101 lines
4.2 KiB
Rust
//! `PrivacyGate` — monotonic class transitions for `BfldFrame`. ADR-120 §2.4.
|
|
//!
|
|
//! The only way a higher-information frame becomes a lower-information frame
|
|
//! is through [`PrivacyGate::demote`]. This function:
|
|
//!
|
|
//! 1. Asserts the target class is **strictly higher in numerical value** (or
|
|
//! equal) to the current class — going from Derived(1) to Anonymous(2) is
|
|
//! a demote; going from Anonymous(2) back to Derived(1) is forbidden.
|
|
//! 2. Zeroes payload sections that are not permitted at the target class,
|
|
//! using a `black_box`-guarded loop to defeat dead-store elimination.
|
|
//! 3. Re-syncs `header.privacy_class` and `header.payload_crc32`.
|
|
//! 4. Returns the new frame.
|
|
//!
|
|
//! There is no `promote` operation by design — once a section is zeroed, the
|
|
//! original bytes are unrecoverable.
|
|
|
|
#![cfg(feature = "std")]
|
|
|
|
use crate::frame::crc32_of_payload;
|
|
use crate::{BfldError, BfldFrame, BfldPayload, PrivacyClass};
|
|
|
|
/// Monotonic class transformer. See module docs.
|
|
pub struct PrivacyGate;
|
|
|
|
impl PrivacyGate {
|
|
/// Apply a class demotion in-place: returns a new `BfldFrame` whose
|
|
/// `privacy_class`, payload sections, and CRC match `target`.
|
|
///
|
|
/// Returns [`BfldError::InvalidDemote`] when `target` would *increase*
|
|
/// the information density (lower class number than the source).
|
|
pub fn demote(
|
|
mut frame: BfldFrame,
|
|
target: PrivacyClass,
|
|
) -> Result<BfldFrame, BfldError> {
|
|
let current = PrivacyClass::try_from(frame.header.privacy_class)?;
|
|
if target.as_u8() < current.as_u8() {
|
|
return Err(BfldError::InvalidDemote {
|
|
from: current.as_u8(),
|
|
to: target.as_u8(),
|
|
});
|
|
}
|
|
|
|
// Strip payload sections not permitted at the target class. We only do
|
|
// this when the payload parses cleanly; a malformed payload remains
|
|
// untouched in the bytes (the class byte and CRC still get re-synced).
|
|
if let Ok(mut payload) = frame.parse_payload() {
|
|
if target.as_u8() >= PrivacyClass::Anonymous.as_u8() {
|
|
// Anonymous: drop the compressed angle matrix (identity surface).
|
|
zeroize_then_clear(&mut payload.compressed_angle_matrix);
|
|
// Also drop optional sections that may carry identity-leaky
|
|
// signal under high-separability conditions.
|
|
if let Some(csi) = payload.csi_delta.as_mut() {
|
|
zeroize_then_clear(csi);
|
|
}
|
|
}
|
|
if target.as_u8() >= PrivacyClass::Restricted.as_u8() {
|
|
// Restricted: also drop amplitude + phase proxies.
|
|
zeroize_then_clear(&mut payload.amplitude_proxy);
|
|
zeroize_then_clear(&mut payload.phase_proxy);
|
|
}
|
|
// Note: csi_delta dropped above implies the flag bit should clear.
|
|
// from_payload re-derives the flag from csi_delta.is_some(), so
|
|
// taking the Option out below ensures the bit is cleared.
|
|
if target.as_u8() >= PrivacyClass::Anonymous.as_u8() {
|
|
payload.csi_delta = None;
|
|
}
|
|
frame = BfldFrame::from_payload(frame.header, &payload);
|
|
}
|
|
|
|
frame.header.privacy_class = target.as_u8();
|
|
// from_payload already recomputed CRC, but recompute again so the
|
|
// path that skipped payload parsing still produces a consistent frame.
|
|
frame.header.payload_crc32 = crc32_of_payload(&frame.payload);
|
|
Ok(frame)
|
|
}
|
|
}
|
|
|
|
/// Overwrite `v` with zeros, then truncate. The `black_box` call defeats
|
|
/// dead-store elimination so the writes are observable.
|
|
fn zeroize_then_clear(v: &mut Vec<u8>) {
|
|
for b in v.iter_mut() {
|
|
*b = 0;
|
|
}
|
|
core::hint::black_box(v.as_ptr());
|
|
v.clear();
|
|
}
|
|
|
|
// Convenience constructor: the gate is a unit type, but keeping a Default
|
|
// makes downstream injection sites (PrivacyGate.demote(...) vs static call)
|
|
// straightforward.
|
|
impl Default for PrivacyGate {
|
|
fn default() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
/// Discard the rest of an unused (#[allow(dead_code)]) — placeholder so
|
|
/// `BfldPayload` import isn't unused in builds that strip the implementation.
|
|
#[allow(dead_code)]
|
|
fn _unused_payload_marker(_: BfldPayload) {}
|