Skip to main content

flac_io/
metadata.rs

1// The stream header: the `fLaC` marker and the metadata block chain.
2//
3// A FLAC stream opens with the four bytes `fLaC`, then one or more metadata
4// blocks. The first block is always STREAMINFO and carries everything the
5// decoder needs: block-size bounds, sample rate, channel count, bit depth,
6// total sample count, and an MD5 of the original samples. Every other block
7// type (padding, seek table, comments, pictures, and so on) is skipped here;
8// the decoder only needs STREAMINFO. The audio frames begin on the byte after
9// the block whose "last" flag is set.
10
11use crate::bitstream::BitReader;
12use crate::error::FlacError;
13
14/// The four-byte FLAC stream marker.
15pub(crate) const FLAC_MARKER: &[u8; 4] = b"fLaC";
16
17/// The fixed length of a STREAMINFO block body.
18const STREAMINFO_LEN: usize = 34;
19
20/// The contents of the STREAMINFO metadata block.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct StreamInfo {
23    pub min_block_size: u16,
24    pub max_block_size: u16,
25    pub min_frame_size: u32,
26    pub max_frame_size: u32,
27    pub sample_rate: u32,
28    pub channels: u8,
29    pub bits_per_sample: u8,
30    /// Total samples per channel. Zero means the encoder did not record it.
31    pub total_samples: u64,
32    /// MD5 of the unencoded samples, or all zero if not recorded.
33    pub md5: [u8; 16],
34}
35
36/// Result of reading the stream header: the STREAMINFO and the byte offset at
37/// which the first audio frame begins.
38#[derive(Debug)]
39pub(crate) struct Header {
40    pub stream_info: StreamInfo,
41    pub frame_start: usize,
42}
43
44/// Parse the `fLaC` marker and the metadata block chain, returning STREAMINFO
45/// and the offset of the first frame.
46pub(crate) fn read_header(bytes: &[u8]) -> Result<Header, FlacError> {
47    if bytes.len() < 4 || &bytes[0..4] != FLAC_MARKER {
48        return Err(FlacError::NotFlac);
49    }
50    let mut pos = 4;
51    let mut stream_info: Option<StreamInfo> = None;
52
53    loop {
54        // Each block header is 4 bytes: 1 last-block bit, 7 type bits, 24 length.
55        if pos + 4 > bytes.len() {
56            return Err(FlacError::Truncated);
57        }
58        let header = bytes[pos];
59        let is_last = header & 0x80 != 0;
60        let block_type = header & 0x7F;
61        let length = ((bytes[pos + 1] as usize) << 16)
62            | ((bytes[pos + 2] as usize) << 8)
63            | bytes[pos + 3] as usize;
64        pos += 4;
65        // checked_add so a crafted length near usize::MAX cannot wrap past the
66        // bounds test and trigger an out-of-range slice.
67        let end = pos.checked_add(length).ok_or(FlacError::Truncated)?;
68        if end > bytes.len() {
69            return Err(FlacError::Truncated);
70        }
71
72        if block_type == 0 {
73            // STREAMINFO.
74            if length != STREAMINFO_LEN {
75                return Err(FlacError::CorruptStream(format!(
76                    "STREAMINFO length is {length}, expected {STREAMINFO_LEN}"
77                )));
78            }
79            if stream_info.is_some() {
80                return Err(FlacError::CorruptStream(
81                    "more than one STREAMINFO block".into(),
82                ));
83            }
84            stream_info = Some(parse_stream_info(&bytes[pos..pos + length])?);
85        } else if block_type == 127 {
86            return Err(FlacError::CorruptStream(
87                "invalid metadata block type 127".into(),
88            ));
89        }
90        // All other block types are skipped.
91
92        pos += length;
93        if is_last {
94            break;
95        }
96    }
97
98    let stream_info =
99        stream_info.ok_or_else(|| FlacError::CorruptStream("no STREAMINFO block found".into()))?;
100    Ok(Header {
101        stream_info,
102        frame_start: pos,
103    })
104}
105
106fn parse_stream_info(body: &[u8]) -> Result<StreamInfo, FlacError> {
107    let mut r = BitReader::new(body);
108    let min_block_size = r.read_u32(16)? as u16;
109    let max_block_size = r.read_u32(16)? as u16;
110    let min_frame_size = r.read_u32(24)?;
111    let max_frame_size = r.read_u32(24)?;
112    let sample_rate = r.read_u32(20)?;
113    let channels = r.read_u32(3)? as u8 + 1;
114    let bits_per_sample = r.read_u32(5)? as u8 + 1;
115    let total_samples = r.read_u64(36)?;
116    let mut md5 = [0u8; 16];
117    for b in md5.iter_mut() {
118        *b = r.read_u32(8)? as u8;
119    }
120
121    if sample_rate == 0 {
122        return Err(FlacError::CorruptStream(
123            "STREAMINFO sample rate is zero".into(),
124        ));
125    }
126
127    Ok(StreamInfo {
128        min_block_size,
129        max_block_size,
130        min_frame_size,
131        max_frame_size,
132        sample_rate,
133        channels,
134        bits_per_sample,
135        total_samples,
136        md5,
137    })
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    /// Build a minimal valid header: marker + one last STREAMINFO block.
145    fn synthetic_header() -> Vec<u8> {
146        let mut v = Vec::new();
147        v.extend_from_slice(FLAC_MARKER);
148        // Block header: last flag set, type 0, length 34.
149        v.push(0x80);
150        v.extend_from_slice(&[0x00, 0x00, 0x22]);
151        // STREAMINFO body, 34 bytes.
152        // min/max block size 4096.
153        v.extend_from_slice(&[0x10, 0x00, 0x10, 0x00]);
154        // min/max frame size 0.
155        v.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
156        // sample_rate=44100 (0xAC44) in 20 bits, channels-1=1, bps-1=15.
157        // 44100 = 0x0AC44. Pack 20 bits sample rate, 3 bits channels, 5 bits bps.
158        // 0000_1010_1100_0100_0100 | 001 | 01111
159        // Byte layout: take it bit by bit via a helper below instead.
160        let mut bits = Vec::new();
161        push_bits(&mut bits, 44100, 20);
162        push_bits(&mut bits, 1, 3); // channels - 1 = 1 (stereo)
163        push_bits(&mut bits, 15, 5); // bps - 1 = 15 (16-bit)
164        push_bits(&mut bits, 88200, 36); // total samples
165        let packed = pack(&bits);
166        v.extend_from_slice(&packed);
167        // MD5 (16 bytes of 0xAB).
168        v.extend_from_slice(&[0xAB; 16]);
169        v
170    }
171
172    fn push_bits(out: &mut Vec<u8>, value: u64, n: u32) {
173        for i in (0..n).rev() {
174            out.push(((value >> i) & 1) as u8);
175        }
176    }
177
178    fn pack(bits: &[u8]) -> Vec<u8> {
179        let mut out = vec![0u8; bits.len().div_ceil(8)];
180        for (i, &bit) in bits.iter().enumerate() {
181            if bit != 0 {
182                out[i / 8] |= 1 << (7 - (i % 8));
183            }
184        }
185        out
186    }
187
188    #[test]
189    fn rejects_non_flac() {
190        assert_eq!(read_header(b"RIFFxxxx").unwrap_err(), FlacError::NotFlac);
191        assert_eq!(read_header(b"fL").unwrap_err(), FlacError::NotFlac);
192    }
193
194    #[test]
195    fn parses_synthetic_streaminfo() {
196        let h = read_header(&synthetic_header()).unwrap();
197        let si = &h.stream_info;
198        assert_eq!(si.min_block_size, 4096);
199        assert_eq!(si.max_block_size, 4096);
200        assert_eq!(si.sample_rate, 44100);
201        assert_eq!(si.channels, 2);
202        assert_eq!(si.bits_per_sample, 16);
203        assert_eq!(si.total_samples, 88200);
204        assert_eq!(si.md5, [0xAB; 16]);
205        assert_eq!(h.frame_start, synthetic_header().len());
206    }
207
208    #[test]
209    fn truncated_header_errors() {
210        let full = synthetic_header();
211        assert_eq!(read_header(&full[..10]).unwrap_err(), FlacError::Truncated);
212    }
213
214    #[test]
215    fn rejects_zero_sample_rate() {
216        let mut h = synthetic_header();
217        // Zero the 20-bit sample rate: it starts at byte 4+4+10 = 18.
218        h[18] = 0;
219        h[19] = 0;
220        h[20] &= 0x0F; // clear the top 4 bits of the sample rate's last nibble
221        assert!(matches!(read_header(&h), Err(FlacError::CorruptStream(_))));
222    }
223}