use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use zeroize::Zeroize;
#[derive(Clone, Default)]
pub struct SecureAudioBuffer {
data: Vec<i16>,
}
impl SecureAudioBuffer {
pub fn new() -> Self {
Self { data: Vec::new() }
}
pub fn from_samples(samples: Vec<i16>) -> Self {
Self { data: samples }
}
pub fn with_capacity(cap: usize) -> Self {
Self {
data: Vec::with_capacity(cap),
}
}
pub fn as_slice(&self) -> &[i16] {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut [i16] {
&mut self.data
}
pub fn extend_from_slice(&mut self, samples: &[i16]) {
self.data.extend_from_slice(samples);
}
pub fn append(&mut self, other: &mut SecureAudioBuffer) {
self.data.append(&mut other.data);
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn clear(&mut self) {
self.data.zeroize();
self.data.clear();
}
pub fn into_vec(mut self) -> Vec<i16> {
std::mem::take(&mut self.data)
}
pub fn replace(&mut self, samples: Vec<i16>) {
self.data.zeroize();
self.data = samples;
}
}
impl Drop for SecureAudioBuffer {
fn drop(&mut self) {
self.data.zeroize();
}
}
impl From<Vec<i16>> for SecureAudioBuffer {
fn from(data: Vec<i16>) -> Self {
Self { data }
}
}
impl From<&[i16]> for SecureAudioBuffer {
fn from(samples: &[i16]) -> Self {
Self {
data: samples.to_vec(),
}
}
}
impl Deref for SecureAudioBuffer {
type Target = [i16];
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl DerefMut for SecureAudioBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl std::fmt::Debug for SecureAudioBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecureAudioBuffer")
.field("len", &self.data.len())
.field("samples", &"[REDACTED]")
.finish()
}
}
#[derive(Clone)]
pub struct SecureAudioRing {
data: VecDeque<i16>,
capacity: usize,
}
impl SecureAudioRing {
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: VecDeque::with_capacity(capacity.saturating_add(1)),
capacity,
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn push_frame(&mut self, frame: &[i16]) {
if self.capacity == 0 {
return;
}
self.data.extend(frame.iter().copied());
while self.data.len() > self.capacity {
if let Some(mut s) = self.data.pop_front() {
s.zeroize();
}
}
}
pub fn to_secure_buffer(&self) -> SecureAudioBuffer {
SecureAudioBuffer::from_samples(self.data.iter().copied().collect())
}
pub fn to_vec(&self) -> Vec<i16> {
self.data.iter().copied().collect()
}
pub fn iter(&self) -> impl Iterator<Item = &i16> {
self.data.iter()
}
pub fn clear(&mut self) {
for s in self.data.iter_mut() {
s.zeroize();
}
self.data.clear();
}
}
impl Drop for SecureAudioRing {
fn drop(&mut self) {
self.clear();
}
}
impl std::fmt::Debug for SecureAudioRing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecureAudioRing")
.field("len", &self.data.len())
.field("capacity", &self.capacity)
.field("samples", &"[REDACTED]")
.finish()
}
}
pub fn zeroize_samples(samples: &mut [i16]) {
samples.zeroize();
}
pub fn zeroize_vec(samples: &mut Vec<i16>) {
samples.zeroize();
samples.clear();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secure_buffer_zeroizes_on_drop() {
let mut buf = SecureAudioBuffer::from_samples(vec![1, 2, 3, 4, 5]);
assert_eq!(buf.len(), 5);
let dbg = format!("{buf:?}");
assert!(dbg.contains("REDACTED"));
assert!(!dbg.contains("1, 2, 3"));
buf.clear();
assert!(buf.is_empty());
assert!(buf.as_slice().iter().all(|&s| s == 0) || buf.is_empty());
}
#[test]
fn secure_buffer_into_vec_transfers_ownership() {
let buf = SecureAudioBuffer::from_samples(vec![9, 8, 7]);
let v = buf.into_vec();
assert_eq!(v, vec![9, 8, 7]);
}
#[test]
fn secure_ring_evicts_and_bounds_capacity() {
let mut ring = SecureAudioRing::with_capacity(4);
ring.push_frame(&[1, 2, 3]);
assert_eq!(ring.len(), 3);
ring.push_frame(&[4, 5, 6]);
assert_eq!(ring.len(), 4);
let v = ring.to_vec();
assert_eq!(v, vec![3, 4, 5, 6]);
let dbg = format!("{ring:?}");
assert!(dbg.contains("REDACTED"));
ring.clear();
assert!(ring.is_empty());
}
#[test]
fn secure_ring_zero_capacity_is_noop() {
let mut ring = SecureAudioRing::with_capacity(0);
ring.push_frame(&[1, 2, 3]);
assert!(ring.is_empty());
}
#[test]
fn zeroize_samples_helper() {
let mut v = vec![1i16, 2, 3];
zeroize_samples(&mut v);
assert_eq!(v, vec![0, 0, 0]);
zeroize_vec(&mut v);
assert!(v.is_empty());
}
}