Skip to main content

aurum_core/remote/
wire_format.rs

1//! Fail-closed remote TTS response format binding (JOE-1977).
2//!
3//! Expected wire format is fixed by the request/capability. Content-Type must
4//! agree; missing or contradictory types never default to PCM.
5
6use super::openai_speech::parse_pcm_content_type;
7use crate::audio::EncodedAudioFormat;
8use crate::error::{ProviderError, Result};
9
10/// What the client requested / capability promised for a remote TTS body.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ExpectedWireFormat {
13    /// Raw little-endian signed 16-bit PCM mono or multi-channel.
14    PcmS16Le { sample_rate_hz: u32, channels: u16 },
15    /// MPEG Layer III (normalized via supervised FFmpeg).
16    Mp3,
17    /// RIFF/WAVE (in-process decode).
18    Wav,
19}
20
21impl ExpectedWireFormat {
22    pub fn pcm(sample_rate_hz: u32, channels: u16) -> Self {
23        Self::PcmS16Le {
24            sample_rate_hz,
25            channels: channels.max(1),
26        }
27    }
28}
29
30fn looks_like_text_envelope(body: &[u8]) -> bool {
31    let trimmed = body
32        .iter()
33        .position(|b| !b.is_ascii_whitespace())
34        .map(|i| &body[i..])
35        .unwrap_or(&[]);
36    if trimmed.is_empty() {
37        return false;
38    }
39    trimmed.starts_with(b"{")
40        || trimmed.starts_with(b"[")
41        || trimmed.starts_with(b"<")
42        || trimmed.windows(5).any(|w| w.eq_ignore_ascii_case(b"<html"))
43        || trimmed.windows(5).any(|w| w.eq_ignore_ascii_case(b"<?xml"))
44}
45
46/// Resolve encoded format from expected request format + response Content-Type.
47///
48/// `body` is only inspected for structural contradictions (JSON/HTML/RIFF magic);
49/// it is never echoed into errors.
50pub fn resolve_encoded_format(
51    provider: &str,
52    expected: ExpectedWireFormat,
53    content_type: &str,
54    body: &[u8],
55) -> Result<EncodedAudioFormat> {
56    let ct_raw = content_type.trim();
57    if ct_raw.is_empty() {
58        return Err(ProviderError::InvalidProviderPayload {
59            provider: provider.into(),
60            reason: "missing Content-Type on remote audio response".into(),
61        }
62        .into());
63    }
64
65    let ct_lower = ct_raw.to_ascii_lowercase();
66    let primary = ct_lower.split(';').next().unwrap_or("").trim();
67
68    // Text/JSON envelopes are never valid audio for any expected format.
69    if primary.starts_with("application/json")
70        || primary.starts_with("text/")
71        || primary.starts_with("application/xml")
72        || primary.starts_with("application/xhtml")
73        || looks_like_text_envelope(body)
74    {
75        return Err(ProviderError::InvalidProviderPayload {
76            provider: provider.into(),
77            reason: "remote audio response is not binary audio (JSON/text/HTML envelope)".into(),
78        }
79        .into());
80    }
81
82    match expected {
83        ExpectedWireFormat::PcmS16Le {
84            sample_rate_hz,
85            channels,
86        } => {
87            if primary.contains("mpeg") || primary == "audio/mp3" {
88                return Err(ProviderError::InvalidProviderPayload {
89                    provider: provider.into(),
90                    reason: "expected PCM but Content-Type is MP3".into(),
91                }
92                .into());
93            }
94            if primary.contains("wav") || body.starts_with(b"RIFF") {
95                return Err(ProviderError::InvalidProviderPayload {
96                    provider: provider.into(),
97                    reason: "expected PCM but response looks like WAV".into(),
98                }
99                .into());
100            }
101            if primary == "application/octet-stream" || primary == "binary/octet-stream" {
102                return Err(ProviderError::InvalidProviderPayload {
103                    provider: provider.into(),
104                    reason: "generic Content-Type cannot be accepted as PCM".into(),
105                }
106                .into());
107            }
108            // Allowlisted PCM MIME families.
109            let pcm_ok = primary == "audio/pcm"
110                || primary == "audio/l16"
111                || primary == "audio/l16;rate"
112                || primary.starts_with("audio/pcm")
113                || primary.starts_with("audio/l16");
114            // ElevenLabs and some vendors use bare audio/pcm without params.
115            if !pcm_ok && primary != "audio/raw" {
116                // Some APIs return "audio/s16le" or similar.
117                if primary != "audio/s16le" && primary != "audio/x-raw" {
118                    return Err(ProviderError::InvalidProviderPayload {
119                        provider: provider.into(),
120                        reason: format!(
121                            "Content-Type '{primary}' is not an allowlisted PCM type for requested PCM"
122                        ),
123                    }
124                    .into());
125                }
126            }
127
128            // If rate/channels appear in Content-Type, they must match expectation.
129            if let Some((rate, ch)) = parse_pcm_content_type(ct_raw) {
130                if rate != sample_rate_hz {
131                    return Err(ProviderError::InvalidProviderPayload {
132                        provider: provider.into(),
133                        reason: format!(
134                            "PCM rate {rate} does not match requested {sample_rate_hz}"
135                        ),
136                    }
137                    .into());
138                }
139                if ch != channels {
140                    return Err(ProviderError::InvalidProviderPayload {
141                        provider: provider.into(),
142                        reason: format!("PCM channels {ch} do not match requested {channels}"),
143                    }
144                    .into());
145                }
146            }
147
148            if !body.len().is_multiple_of(2) {
149                return Err(ProviderError::InvalidProviderPayload {
150                    provider: provider.into(),
151                    reason: "PCM body length is not a multiple of 2".into(),
152                }
153                .into());
154            }
155            if body.is_empty() {
156                return Err(ProviderError::InvalidProviderPayload {
157                    provider: provider.into(),
158                    reason: "empty PCM body".into(),
159                }
160                .into());
161            }
162
163            Ok(EncodedAudioFormat::PcmS16Le {
164                sample_rate_hz,
165                channels,
166            })
167        }
168        ExpectedWireFormat::Mp3 => {
169            let mp3_ok = primary.contains("mpeg")
170                || primary == "audio/mp3"
171                || primary == "audio/x-mp3"
172                || primary == "audio/mpeg3";
173            if !mp3_ok {
174                return Err(ProviderError::InvalidProviderPayload {
175                    provider: provider.into(),
176                    reason: format!(
177                        "Content-Type '{primary}' is not allowlisted MP3 for requested MP3"
178                    ),
179                }
180                .into());
181            }
182            Ok(EncodedAudioFormat::Mp3)
183        }
184        ExpectedWireFormat::Wav => {
185            let wav_ok = primary == "audio/wav"
186                || primary == "audio/wave"
187                || primary == "audio/x-wav"
188                || primary == "audio/vnd.wave";
189            if !wav_ok {
190                return Err(ProviderError::InvalidProviderPayload {
191                    provider: provider.into(),
192                    reason: format!(
193                        "Content-Type '{primary}' is not allowlisted WAV for requested WAV"
194                    ),
195                }
196                .into());
197            }
198            if body.len() >= 4 && !body.starts_with(b"RIFF") {
199                return Err(ProviderError::InvalidProviderPayload {
200                    provider: provider.into(),
201                    reason: "WAV Content-Type without RIFF magic".into(),
202                }
203                .into());
204            }
205            Ok(EncodedAudioFormat::Wav)
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn pcm_requires_allowlisted_mime() {
216        let exp = ExpectedWireFormat::pcm(24_000, 1);
217        let pcm = [0u8; 4];
218        assert!(resolve_encoded_format("t", exp, "audio/pcm;rate=24000;channels=1", &pcm).is_ok());
219        assert!(resolve_encoded_format("t", exp, "", &pcm).is_err());
220        assert!(resolve_encoded_format("t", exp, "application/octet-stream", &pcm).is_err());
221        assert!(resolve_encoded_format("t", exp, "application/json", b"{}").is_err());
222        assert!(resolve_encoded_format("t", exp, "audio/mpeg", &pcm).is_err());
223        assert!(resolve_encoded_format("t", exp, "audio/pcm", b"RIFF....").is_err());
224        assert!(resolve_encoded_format("t", exp, "text/html", b"<html>").is_err());
225        // Rate mismatch
226        assert!(resolve_encoded_format("t", exp, "audio/pcm;rate=16000;channels=1", &pcm).is_err());
227    }
228
229    #[test]
230    fn json_body_never_pcm() {
231        let exp = ExpectedWireFormat::pcm(24_000, 1);
232        let err =
233            resolve_encoded_format("t", exp, "audio/pcm", b"{\"error\":\"nope\"}").unwrap_err();
234        let s = err.to_string();
235        assert!(!s.contains("nope")); // no body echo
236        assert!(s.contains("envelope") || s.contains("not binary"));
237    }
238
239    #[test]
240    fn mp3_and_wav_allowlists() {
241        assert!(resolve_encoded_format(
242            "t",
243            ExpectedWireFormat::Mp3,
244            "audio/mpeg",
245            &[0xFF, 0xFB, 0, 0]
246        )
247        .is_ok());
248        assert!(
249            resolve_encoded_format("t", ExpectedWireFormat::Wav, "audio/wav", b"RIFF....WAVE")
250                .is_ok()
251        );
252        assert!(
253            resolve_encoded_format("t", ExpectedWireFormat::Wav, "audio/wav", b"not-riff").is_err()
254        );
255    }
256}