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