Skip to main content

codec_core/utils/
validation.rs

1//! Input validation utilities for codec operations
2
3use crate::error::{CodecError, Result};
4use crate::types::{CodecType, SampleRate};
5
6/// Validate audio samples for codec processing
7///
8/// # Errors
9///
10/// Returns an error when `samples` is empty.
11pub fn validate_samples(samples: &[i16]) -> Result<()> {
12    if samples.is_empty() {
13        return Err(CodecError::invalid_format("Input samples cannot be empty"));
14    }
15    // Per-sample range check removed: every `i16` is by definition
16    // within [-32768, 32767], and `i16::MIN.abs()` would overflow.
17    Ok(())
18}
19
20/// Validate encoded data for codec processing
21///
22/// # Errors
23///
24/// Returns an error when `data` is empty or exceeds the supported size limit.
25pub fn validate_encoded_data(data: &[u8]) -> Result<()> {
26    if data.is_empty() {
27        return Err(CodecError::invalid_format("Encoded data cannot be empty"));
28    }
29
30    // Check for reasonable data size (not too large)
31    if data.len() > 1024 * 1024 {
32        return Err(CodecError::invalid_format(format!(
33            "Encoded data too large: {} bytes",
34            data.len()
35        )));
36    }
37
38    Ok(())
39}
40
41/// Validate frame size for a specific codec
42///
43/// # Errors
44///
45/// Returns an error when `frame_size` is unsupported by `codec_type`.
46pub fn validate_frame_size(codec_type: CodecType, frame_size: usize) -> Result<()> {
47    let expected_sizes = match codec_type {
48        CodecType::G711Pcmu | CodecType::G711Pcma => {
49            // G.711 commonly uses 10ms or 20ms frames at 8kHz
50            vec![80, 160, 240, 320]
51        }
52
53        CodecType::G729 | CodecType::G729A | CodecType::G729BA => {
54            // G.729/G.729A/G.729BA use fixed 10ms frames at 8kHz
55            vec![80]
56        }
57        CodecType::Opus => {
58            // Opus supports various frame sizes
59            vec![120, 240, 480, 960, 1920, 2880]
60        }
61        // AMR is fixed at 20 ms: 160 samples at 8 kHz, 320 at 16 kHz.
62        CodecType::AmrNb => vec![160],
63        CodecType::AmrWb => vec![320],
64    };
65
66    if !expected_sizes.contains(&frame_size) {
67        return Err(CodecError::InvalidFrameSize {
68            expected: expected_sizes[0],
69            actual: frame_size,
70        });
71    }
72
73    Ok(())
74}
75
76/// Validate sample rate for a specific codec
77///
78/// # Errors
79///
80/// Returns an error when `sample_rate` is unsupported by `codec_type`.
81pub fn validate_sample_rate(codec_type: CodecType, sample_rate: SampleRate) -> Result<()> {
82    let supported_rates = codec_type.supported_sample_rates();
83    let rate_hz = sample_rate.hz();
84
85    if !supported_rates.contains(&rate_hz) {
86        return Err(CodecError::InvalidSampleRate {
87            rate: rate_hz,
88            supported: supported_rates.to_vec(),
89        });
90    }
91
92    Ok(())
93}
94
95/// Validate channel count for a specific codec
96///
97/// # Errors
98///
99/// Returns an error when `channels` is unsupported by `codec_type`.
100pub fn validate_channels(codec_type: CodecType, channels: u8) -> Result<()> {
101    let supported_channels = codec_type.supported_channels();
102
103    if !supported_channels.contains(&channels) {
104        return Err(CodecError::InvalidChannelCount {
105            channels,
106            supported: supported_channels.to_vec(),
107        });
108    }
109
110    Ok(())
111}
112
113/// Validate bitrate for a specific codec
114///
115/// # Errors
116///
117/// Returns an error when `bitrate` is outside the codec's supported range.
118pub const fn validate_bitrate(codec_type: CodecType, bitrate: u32) -> Result<()> {
119    let (min_bitrate, max_bitrate) = codec_type.bitrate_range();
120
121    if bitrate < min_bitrate || bitrate > max_bitrate {
122        return Err(CodecError::InvalidBitrate {
123            bitrate,
124            min: min_bitrate,
125            max: max_bitrate,
126        });
127    }
128
129    Ok(())
130}
131
132/// Validate buffer sizes for encoding/decoding operations
133///
134/// # Errors
135///
136/// Returns an error when `output_size` is smaller than the size implied by
137/// `input_size` and `expected_ratio`.
138#[allow(
139    clippy::cast_possible_truncation,
140    clippy::cast_precision_loss,
141    clippy::cast_sign_loss
142)]
143pub fn validate_buffer_sizes(
144    input_size: usize,
145    output_size: usize,
146    expected_ratio: f32,
147) -> Result<()> {
148    let expected_output_size = (input_size as f32 * expected_ratio) as usize;
149
150    if output_size < expected_output_size {
151        return Err(CodecError::BufferTooSmall {
152            needed: expected_output_size,
153            actual: output_size,
154        });
155    }
156
157    Ok(())
158}
159
160/// Validate that frame samples are properly aligned for multi-channel audio
161///
162/// # Errors
163///
164/// Returns an error when the sample count is not divisible by `channels`.
165#[allow(clippy::manual_is_multiple_of)]
166pub fn validate_channel_alignment(samples: &[i16], channels: u8) -> Result<()> {
167    if samples.len() % usize::from(channels) != 0 {
168        return Err(CodecError::invalid_format(format!(
169            "Sample count {} not divisible by channel count {}",
170            samples.len(),
171            channels
172        )));
173    }
174
175    Ok(())
176}
177
178/// Validate G.711 specific parameters
179///
180/// # Errors
181///
182/// Returns an error when the frame is empty or has the wrong sample count.
183pub fn validate_g711_frame(samples: &[i16], expected_frame_size: usize) -> Result<()> {
184    validate_samples(samples)?;
185
186    if samples.len() != expected_frame_size {
187        return Err(CodecError::InvalidFrameSize {
188            expected: expected_frame_size,
189            actual: samples.len(),
190        });
191    }
192
193    Ok(())
194}
195
196/// Validate G.722 specific parameters
197///
198/// # Errors
199///
200/// Returns an error when the frame is empty, has the wrong sample count, or
201/// contains an odd number of samples.
202pub fn validate_g722_frame(samples: &[i16], expected_frame_size: usize) -> Result<()> {
203    validate_samples(samples)?;
204
205    if samples.len() != expected_frame_size {
206        return Err(CodecError::InvalidFrameSize {
207            expected: expected_frame_size,
208            actual: samples.len(),
209        });
210    }
211
212    // G.722 requires even number of samples for QMF processing
213    if !samples.len().is_multiple_of(2) {
214        return Err(CodecError::invalid_format(
215            "G.722 requires even number of samples for QMF processing",
216        ));
217    }
218
219    Ok(())
220}
221
222/// Validate G.729 specific parameters
223///
224/// # Errors
225///
226/// Returns an error when the frame is empty or does not contain 80 samples.
227pub fn validate_g729_frame(samples: &[i16]) -> Result<()> {
228    validate_samples(samples)?;
229
230    // G.729 uses fixed 80-sample frames (10ms at 8kHz)
231    if samples.len() != 80 {
232        return Err(CodecError::InvalidFrameSize {
233            expected: 80,
234            actual: samples.len(),
235        });
236    }
237
238    Ok(())
239}
240
241/// Validate Opus specific parameters
242///
243/// # Errors
244///
245/// Returns an error when the frame is empty, its sample rate is unsupported,
246/// or its sample count is invalid for the selected rate.
247pub fn validate_opus_frame(samples: &[i16], sample_rate: SampleRate) -> Result<()> {
248    validate_samples(samples)?;
249
250    let rate_hz = sample_rate.hz();
251    let frame_size = samples.len();
252
253    // Opus supports specific frame sizes based on sample rate
254    let valid_frame_sizes = match rate_hz {
255        8000 => vec![20, 40, 80, 160, 320, 480],
256        12000 => vec![30, 60, 120, 240, 480, 720],
257        16000 => vec![40, 80, 160, 320, 640, 960],
258        24000 => vec![60, 120, 240, 480, 960, 1440],
259        48000 => vec![120, 240, 480, 960, 1920, 2880],
260        _ => {
261            return Err(CodecError::InvalidSampleRate {
262                rate: rate_hz,
263                supported: vec![8000, 12000, 16000, 24000, 48000],
264            });
265        }
266    };
267
268    if !valid_frame_sizes.contains(&frame_size) {
269        return Err(CodecError::InvalidFrameSize {
270            expected: valid_frame_sizes[0],
271            actual: frame_size,
272        });
273    }
274
275    Ok(())
276}
277
278/// Validate that two buffers have compatible sizes for processing
279///
280/// # Errors
281///
282/// Returns an error when `output` is smaller than the size implied by the
283/// input length and `compression_ratio`.
284#[allow(
285    clippy::cast_possible_truncation,
286    clippy::cast_precision_loss,
287    clippy::cast_sign_loss
288)]
289pub fn validate_buffer_compatibility(
290    input: &[i16],
291    output: &[u8],
292    compression_ratio: f32,
293) -> Result<()> {
294    let expected_output_size = (input.len() as f32 * compression_ratio) as usize;
295
296    if output.len() < expected_output_size {
297        return Err(CodecError::BufferTooSmall {
298            needed: expected_output_size,
299            actual: output.len(),
300        });
301    }
302
303    Ok(())
304}
305
306/// Validate memory alignment for SIMD operations
307///
308/// # Errors
309///
310/// This check currently reports misalignment through tracing and always
311/// succeeds; the result type is retained for API compatibility.
312pub fn validate_simd_alignment(data: &[i16]) -> Result<()> {
313    let ptr = data.as_ptr() as usize;
314
315    // Check for 16-byte alignment (required for SSE)
316    if !ptr.is_multiple_of(16) {
317        tracing::debug!("Data not aligned for SIMD operations, falling back to scalar");
318    }
319
320    Ok(())
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use crate::types::SampleRate;
327
328    #[test]
329    fn test_validate_samples() {
330        let valid_samples = vec![0, 1000, -1000, 16000, -16000];
331        assert!(validate_samples(&valid_samples).is_ok());
332
333        let empty_samples: Vec<i16> = vec![];
334        assert!(validate_samples(&empty_samples).is_err());
335    }
336
337    #[test]
338    fn test_validate_encoded_data() {
339        let valid_data = vec![0u8, 127, 255, 64, 192];
340        assert!(validate_encoded_data(&valid_data).is_ok());
341
342        let empty_data: Vec<u8> = vec![];
343        assert!(validate_encoded_data(&empty_data).is_err());
344
345        let too_large_data = vec![0u8; 2 * 1024 * 1024]; // 2MB
346        assert!(validate_encoded_data(&too_large_data).is_err());
347    }
348
349    #[test]
350    fn test_validate_frame_size() {
351        // G.711 valid frame sizes
352        assert!(validate_frame_size(CodecType::G711Pcmu, 160).is_ok());
353        assert!(validate_frame_size(CodecType::G711Pcmu, 123).is_err());
354
355        // G.729 fixed frame size
356        assert!(validate_frame_size(CodecType::G729, 80).is_ok());
357        assert!(validate_frame_size(CodecType::G729, 160).is_err());
358    }
359
360    #[test]
361    fn test_validate_sample_rate() {
362        // G.711 supports only 8kHz
363        assert!(validate_sample_rate(CodecType::G711Pcmu, SampleRate::Rate8000).is_ok());
364        assert!(validate_sample_rate(CodecType::G711Pcmu, SampleRate::Rate48000).is_err());
365
366        // Opus supports multiple rates
367        assert!(validate_sample_rate(CodecType::Opus, SampleRate::Rate8000).is_ok());
368        assert!(validate_sample_rate(CodecType::Opus, SampleRate::Rate48000).is_ok());
369    }
370
371    #[test]
372    fn test_validate_channels() {
373        // G.711 supports only mono
374        assert!(validate_channels(CodecType::G711Pcmu, 1).is_ok());
375        assert!(validate_channels(CodecType::G711Pcmu, 2).is_err());
376
377        // Opus supports mono and stereo
378        assert!(validate_channels(CodecType::Opus, 1).is_ok());
379        assert!(validate_channels(CodecType::Opus, 2).is_ok());
380        assert!(validate_channels(CodecType::Opus, 3).is_err());
381    }
382
383    #[test]
384    fn test_validate_bitrate() {
385        // G.711 has fixed bitrate
386        assert!(validate_bitrate(CodecType::G711Pcmu, 64_000).is_ok());
387        assert!(validate_bitrate(CodecType::G711Pcmu, 128_000).is_err());
388
389        // Opus has variable bitrate
390        assert!(validate_bitrate(CodecType::Opus, 32_000).is_ok());
391        assert!(validate_bitrate(CodecType::Opus, 600_000).is_err());
392    }
393
394    #[test]
395    fn test_validate_buffer_sizes() {
396        // G.711 has 1:1 compression ratio (16-bit to 8-bit)
397        assert!(validate_buffer_sizes(160, 80, 0.5).is_ok());
398        assert!(validate_buffer_sizes(160, 40, 0.5).is_err());
399    }
400
401    #[test]
402    fn test_validate_channel_alignment() {
403        let mono_samples = vec![0, 1, 2, 3, 4]; // 5 samples
404        assert!(validate_channel_alignment(&mono_samples, 1).is_ok());
405        assert!(validate_channel_alignment(&mono_samples, 2).is_err());
406
407        let stereo_samples = vec![0, 1, 2, 3]; // 4 samples = 2 stereo pairs
408        assert!(validate_channel_alignment(&stereo_samples, 2).is_ok());
409    }
410
411    #[test]
412    fn test_codec_specific_validation() {
413        // G.711 frame validation
414        let g711_frame = vec![0i16; 160];
415        assert!(validate_g711_frame(&g711_frame, 160).is_ok());
416        assert!(validate_g711_frame(&g711_frame, 80).is_err());
417
418        // G.729 frame validation
419        let g729_frame = vec![0i16; 80];
420        assert!(validate_g729_frame(&g729_frame).is_ok());
421
422        let wrong_g729_frame = vec![0i16; 160];
423        assert!(validate_g729_frame(&wrong_g729_frame).is_err());
424    }
425
426    #[test]
427    fn test_buffer_compatibility() {
428        let input = vec![0i16; 160];
429        let output = vec![0u8; 80];
430
431        // G.711 compression ratio: 0.5 (16-bit to 8-bit)
432        assert!(validate_buffer_compatibility(&input, &output, 0.5).is_ok());
433
434        let small_output = vec![0u8; 40];
435        assert!(validate_buffer_compatibility(&input, &small_output, 0.5).is_err());
436    }
437}