use anyhow::{Result, bail};
use snapcast_proto::SampleFormat;
use snapcast_proto::message::codec_header::CodecHeader;
use crate::decoder::Decoder;
const MAGIC: &[u8; 4] = b"F32L";
pub struct F32Lz4Decoder {
sample_format: SampleFormat,
#[cfg(feature = "encryption")]
decryptor: Option<crate::crypto::ChunkDecryptor>,
#[cfg(feature = "encryption")]
encryption_psk: Option<String>,
}
impl Decoder for F32Lz4Decoder {
fn set_header(&mut self, header: &CodecHeader) -> Result<SampleFormat> {
if header.payload.len() < 12 {
bail!("F32LZ4 header too small");
}
if &header.payload[..4] != MAGIC {
bail!("not an F32LZ4 header");
}
let rate = u32::from_le_bytes(header.payload[4..8].try_into().unwrap());
let channels = u16::from_le_bytes(header.payload[8..10].try_into().unwrap());
let bits = u16::from_le_bytes(header.payload[10..12].try_into().unwrap());
self.sample_format = SampleFormat::new(rate, bits, channels);
#[cfg(feature = "encryption")]
if header.payload.len() >= 32 && &header.payload[12..16] == b"ENC\0" {
let salt = &header.payload[16..32];
if let Some(ref psk) = self.encryption_psk {
self.decryptor = Some(crate::crypto::ChunkDecryptor::new(psk, salt));
tracing::info!("F32LZ4 decryption enabled");
} else {
bail!("Server requires encryption but no encryption_psk configured");
}
}
tracing::info!(rate, channels, bits, "F32LZ4 decoder initialized");
Ok(self.sample_format)
}
fn decode(&mut self, data: &mut Vec<u8>) -> Result<bool> {
if data.is_empty() {
return Ok(false);
}
#[cfg(feature = "encryption")]
if let Some(ref dec) = self.decryptor {
match dec.decrypt(data) {
Ok(decrypted) => *data = decrypted,
Err(e) => {
tracing::warn!(error = %e, "F32LZ4 decryption failed");
return Ok(false);
}
}
}
match lz4_flex::decompress_size_prepended(data) {
Ok(decompressed) => {
tracing::trace!(
compressed = data.len(),
decompressed = decompressed.len(),
"F32LZ4 decoded"
);
*data = decompressed;
Ok(true)
}
Err(e) => {
tracing::warn!(error = %e, "F32LZ4 decompress failed");
Ok(false)
}
}
}
}
#[cfg(feature = "encryption")]
pub fn create(encryption_psk: Option<&str>) -> F32Lz4Decoder {
F32Lz4Decoder {
sample_format: SampleFormat::default(),
decryptor: None,
encryption_psk: encryption_psk.map(String::from),
}
}
#[cfg(not(feature = "encryption"))]
pub fn create() -> F32Lz4Decoder {
F32Lz4Decoder {
sample_format: SampleFormat::default(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let _fmt = SampleFormat::new(48000, 16, 2);
let f32_samples: Vec<f32> = (0..960).map(|i| (i as f32 / 960.0) * 2.0 - 1.0).collect();
let f32_bytes: Vec<u8> = f32_samples.iter().flat_map(|s| s.to_le_bytes()).collect();
let compressed = lz4_flex::compress_prepend_size(&f32_bytes);
#[cfg(feature = "encryption")]
let mut dec = create(None);
#[cfg(not(feature = "encryption"))]
let mut dec = create();
let header = CodecHeader {
codec: "f32lz4".into(),
payload: {
let mut h = Vec::new();
h.extend_from_slice(MAGIC);
h.extend_from_slice(&48000u32.to_le_bytes());
h.extend_from_slice(&2u16.to_le_bytes());
h.extend_from_slice(&32u16.to_le_bytes());
h
},
};
let sf = dec.set_header(&header).unwrap();
assert_eq!(sf.rate(), 48000);
let mut data = compressed;
assert!(dec.decode(&mut data).unwrap());
assert_eq!(data, f32_bytes);
}
}