snapcast_client/decoder/
f32lz4.rs1use anyhow::{Result, bail};
11use snapcast_proto::SampleFormat;
12use snapcast_proto::message::codec_header::CodecHeader;
13
14use crate::decoder::Decoder;
15
16const MAGIC: &[u8; 4] = b"F32L";
17
18pub struct F32Lz4Decoder {
20 sample_format: SampleFormat,
21 #[cfg(feature = "encryption")]
22 decryptor: Option<crate::crypto::ChunkDecryptor>,
23 #[cfg(feature = "encryption")]
24 encryption_psk: Option<String>,
25}
26
27impl Decoder for F32Lz4Decoder {
28 fn set_header(&mut self, header: &CodecHeader) -> Result<SampleFormat> {
29 if header.payload.len() < 12 {
30 bail!("F32LZ4 header too small");
31 }
32 if &header.payload[..4] != MAGIC {
33 bail!("not an F32LZ4 header");
34 }
35 let rate = u32::from_le_bytes(header.payload[4..8].try_into().unwrap());
36 let channels = u16::from_le_bytes(header.payload[8..10].try_into().unwrap());
37 let bits = u16::from_le_bytes(header.payload[10..12].try_into().unwrap());
38 self.sample_format = SampleFormat::new(rate, bits, channels);
39
40 #[cfg(feature = "encryption")]
42 if header.payload.len() >= 32 && &header.payload[12..16] == b"ENC\0" {
43 let salt = &header.payload[16..32];
44 if let Some(ref psk) = self.encryption_psk {
45 self.decryptor = Some(crate::crypto::ChunkDecryptor::new(psk, salt));
46 tracing::info!("F32LZ4 decryption enabled");
47 } else {
48 bail!("Server requires encryption but no encryption_psk configured");
49 }
50 }
51
52 tracing::info!(rate, channels, bits, "F32LZ4 decoder initialized");
53 Ok(self.sample_format)
54 }
55
56 fn decode(&mut self, data: &mut Vec<u8>) -> Result<bool> {
57 if data.is_empty() {
58 return Ok(false);
59 }
60
61 #[cfg(feature = "encryption")]
63 if let Some(ref dec) = self.decryptor {
64 match dec.decrypt(data) {
65 Ok(decrypted) => *data = decrypted,
66 Err(e) => {
67 tracing::warn!(error = %e, "F32LZ4 decryption failed");
68 return Ok(false);
69 }
70 }
71 }
72
73 match lz4_flex::decompress_size_prepended(data) {
74 Ok(decompressed) => {
75 tracing::trace!(
76 compressed = data.len(),
77 decompressed = decompressed.len(),
78 "F32LZ4 decoded"
79 );
80 *data = decompressed;
81 Ok(true)
82 }
83 Err(e) => {
84 tracing::warn!(error = %e, "F32LZ4 decompress failed");
85 Ok(false)
86 }
87 }
88 }
89}
90
91#[cfg(feature = "encryption")]
93pub fn create(encryption_psk: Option<&str>) -> F32Lz4Decoder {
94 F32Lz4Decoder {
95 sample_format: SampleFormat::default(),
96 decryptor: None,
97 encryption_psk: encryption_psk.map(String::from),
98 }
99}
100
101#[cfg(not(feature = "encryption"))]
103pub fn create() -> F32Lz4Decoder {
104 F32Lz4Decoder {
105 sample_format: SampleFormat::default(),
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn roundtrip() {
115 let _fmt = SampleFormat::new(48000, 16, 2);
116
117 let f32_samples: Vec<f32> = (0..960).map(|i| (i as f32 / 960.0) * 2.0 - 1.0).collect();
119 let f32_bytes: Vec<u8> = f32_samples.iter().flat_map(|s| s.to_le_bytes()).collect();
120 let compressed = lz4_flex::compress_prepend_size(&f32_bytes);
121
122 #[cfg(feature = "encryption")]
124 let mut dec = create(None);
125 #[cfg(not(feature = "encryption"))]
126 let mut dec = create();
127 let header = CodecHeader {
128 codec: "f32lz4".into(),
129 payload: {
130 let mut h = Vec::new();
131 h.extend_from_slice(MAGIC);
132 h.extend_from_slice(&48000u32.to_le_bytes());
133 h.extend_from_slice(&2u16.to_le_bytes());
134 h.extend_from_slice(&32u16.to_le_bytes());
135 h
136 },
137 };
138 let sf = dec.set_header(&header).unwrap();
139 assert_eq!(sf.rate(), 48000);
140
141 let mut data = compressed;
142 assert!(dec.decode(&mut data).unwrap());
143 assert_eq!(data, f32_bytes);
144 }
145}