Skip to main content

dsf/
header.rs

1use std::io::{self, Read, Write};
2
3use crate::{Error, Result};
4
5const DSD_CHUNK_LEN: u64 = 28;
6const FMT_CHUNK_LEN: u64 = 52;
7/// Size of the `data` chunk header, which `data_chunk_size` counts along with the audio.
8pub const DATA_CHUNK_HEADER_LEN: u64 = 12;
9
10/// Offset of the first audio byte in a DSF file (92).
11pub const AUDIO_OFFSET: u64 = DSD_CHUNK_LEN + FMT_CHUNK_LEN + DATA_CHUNK_HEADER_LEN;
12
13/// The fixed-size DSF header: `DSD ` chunk, `fmt ` chunk and `data` chunk header.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct DsfHeader {
16    /// Total file size as recorded in the header.
17    pub total_file_size: u64,
18    /// Offset of the ID3v2 metadata chunk; `0` means no metadata.
19    pub metadata_offset: u64,
20    /// Channel type id (e.g. 2 = stereo).
21    pub channel_type: u32,
22    /// Number of channels.
23    pub channel_count: u32,
24    /// Sampling frequency in Hz (e.g. 2_822_400).
25    pub sample_rate: u32,
26    /// 1 (LSB first) or 8 (MSB first).
27    pub bits_per_sample: u32,
28    /// Samples per channel.
29    pub sample_count: u64,
30    /// Block size per channel in bytes (4096 per spec).
31    pub block_size: u32,
32    /// Size of the data chunk *including* its 12-byte header.
33    pub data_chunk_size: u64,
34}
35
36impl DsfHeader {
37    /// Parses and validates a header from the start of `r`.
38    pub fn read<R: Read>(mut r: R) -> Result<Self> {
39        let r = &mut r;
40        magic(r, b"DSD ", "DSD")?;
41        if u64_le(r)? != DSD_CHUNK_LEN {
42            return Err(Error::Malformed("unexpected DSD chunk size"));
43        }
44        let total_file_size = u64_le(r)?;
45        let metadata_offset = u64_le(r)?;
46
47        magic(r, b"fmt ", "fmt")?;
48        if u64_le(r)? != FMT_CHUNK_LEN {
49            return Err(Error::Malformed("unexpected fmt chunk size"));
50        }
51        if u32_le(r)? != 1 {
52            return Err(Error::Malformed("unsupported DSF version"));
53        }
54        if u32_le(r)? != 0 {
55            return Err(Error::Malformed("unsupported format id (only raw DSD)"));
56        }
57        let channel_type = u32_le(r)?;
58        let channel_count = u32_le(r)?;
59        let sample_rate = u32_le(r)?;
60        let bits_per_sample = u32_le(r)?;
61        let sample_count = u64_le(r)?;
62        let block_size = u32_le(r)?;
63        let _reserved = u32_le(r)?;
64
65        magic(r, b"data", "data")?;
66        let data_chunk_size = u64_le(r)?;
67
68        let header = Self {
69            total_file_size,
70            metadata_offset,
71            channel_type,
72            channel_count,
73            sample_rate,
74            bits_per_sample,
75            sample_count,
76            block_size,
77            data_chunk_size,
78        };
79        header.validate()?;
80        Ok(header)
81    }
82
83    /// Serializes the full 92-byte header (including the `data` chunk header).
84    pub fn write<W: Write>(&self, mut w: W) -> Result<()> {
85        let mut b = Vec::with_capacity(92);
86        b.extend_from_slice(b"DSD ");
87        b.extend_from_slice(&DSD_CHUNK_LEN.to_le_bytes());
88        b.extend_from_slice(&self.total_file_size.to_le_bytes());
89        b.extend_from_slice(&self.metadata_offset.to_le_bytes());
90        b.extend_from_slice(b"fmt ");
91        b.extend_from_slice(&FMT_CHUNK_LEN.to_le_bytes());
92        b.extend_from_slice(&1u32.to_le_bytes()); // version
93        b.extend_from_slice(&0u32.to_le_bytes()); // format id: raw DSD
94        b.extend_from_slice(&self.channel_type.to_le_bytes());
95        b.extend_from_slice(&self.channel_count.to_le_bytes());
96        b.extend_from_slice(&self.sample_rate.to_le_bytes());
97        b.extend_from_slice(&self.bits_per_sample.to_le_bytes());
98        b.extend_from_slice(&self.sample_count.to_le_bytes());
99        b.extend_from_slice(&self.block_size.to_le_bytes());
100        b.extend_from_slice(&0u32.to_le_bytes()); // reserved
101        b.extend_from_slice(b"data");
102        b.extend_from_slice(&self.data_chunk_size.to_le_bytes());
103        w.write_all(&b)?;
104        Ok(())
105    }
106
107    /// Offset one past the last audio byte; where a metadata chunk belongs.
108    pub fn audio_end(&self) -> u64 {
109        (DSD_CHUNK_LEN + FMT_CHUNK_LEN).saturating_add(self.data_chunk_size)
110    }
111
112    fn validate(&self) -> Result<()> {
113        if self.channel_count == 0 {
114            return Err(Error::Malformed("zero channels"));
115        }
116        if self.block_size == 0 {
117            return Err(Error::Malformed("zero block size"));
118        }
119        if !matches!(self.bits_per_sample, 1 | 8) {
120            return Err(Error::Malformed("bits per sample must be 1 or 8"));
121        }
122        if self.data_chunk_size < DATA_CHUNK_HEADER_LEN
123            || (DSD_CHUNK_LEN + FMT_CHUNK_LEN)
124                .checked_add(self.data_chunk_size)
125                .is_none()
126        {
127            return Err(Error::Malformed("invalid data chunk size"));
128        }
129        Ok(())
130    }
131}
132
133fn fill<R: Read, const N: usize>(r: &mut R) -> Result<[u8; N]> {
134    let mut buf = [0u8; N];
135    r.read_exact(&mut buf).map_err(|e| match e.kind() {
136        io::ErrorKind::UnexpectedEof => Error::Malformed("truncated header"),
137        _ => Error::Io(e),
138    })?;
139    Ok(buf)
140}
141
142fn u32_le<R: Read>(r: &mut R) -> Result<u32> {
143    Ok(u32::from_le_bytes(fill(r)?))
144}
145
146fn u64_le<R: Read>(r: &mut R) -> Result<u64> {
147    Ok(u64::from_le_bytes(fill(r)?))
148}
149
150fn magic<R: Read>(r: &mut R, expected: &[u8; 4], name: &'static str) -> Result<()> {
151    if &fill::<R, 4>(r)? == expected {
152        Ok(())
153    } else {
154        Err(Error::BadSignature(name))
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    fn sample() -> DsfHeader {
163        DsfHeader {
164            total_file_size: AUDIO_OFFSET + 8192,
165            metadata_offset: 0,
166            channel_type: 2,
167            channel_count: 2,
168            sample_rate: 2_822_400,
169            bits_per_sample: 1,
170            sample_count: 32_768,
171            block_size: 4096,
172            data_chunk_size: DATA_CHUNK_HEADER_LEN + 8192,
173        }
174    }
175
176    fn encoded() -> Vec<u8> {
177        let mut bytes = Vec::new();
178        assert!(sample().write(&mut bytes).is_ok());
179        bytes
180    }
181
182    #[test]
183    fn round_trips() {
184        assert_eq!(DsfHeader::read(encoded().as_slice()).ok(), Some(sample()));
185    }
186
187    #[test]
188    fn every_truncation_is_an_error_not_a_panic() {
189        let bytes = encoded();
190        for len in 0..bytes.len() {
191            let prefix = bytes.get(..len).unwrap_or_default();
192            assert!(DsfHeader::read(prefix).is_err(), "prefix of {len} bytes");
193        }
194    }
195
196    #[test]
197    fn every_single_byte_corruption_is_handled() {
198        let bytes = encoded();
199        for index in 0..bytes.len() {
200            for flip in [0x01u8, 0x80, 0xFF] {
201                let mut copy = bytes.clone();
202                if let Some(byte) = copy.get_mut(index) {
203                    *byte ^= flip;
204                }
205                // Either rejected or accepted; what matters is that it returns.
206                let _ = DsfHeader::read(copy.as_slice());
207            }
208        }
209    }
210
211    #[test]
212    fn rejects_zero_channels_and_block_size() {
213        for mutate in [
214            |h: &mut DsfHeader| h.channel_count = 0,
215            |h: &mut DsfHeader| h.block_size = 0,
216            |h: &mut DsfHeader| h.bits_per_sample = 4,
217            |h: &mut DsfHeader| h.data_chunk_size = 3,
218        ] {
219            let mut header = sample();
220            mutate(&mut header);
221            let mut bytes = Vec::new();
222            assert!(header.write(&mut bytes).is_ok());
223            assert!(DsfHeader::read(bytes.as_slice()).is_err());
224        }
225    }
226}