l6t_file/
iff.rs

1/// IFF reader/writer adapted from https://github.com/qpliu/iff-rs with
2/// Line6-specific quirks such as little-endian files and unaligned data
3/// chunks.
4///
5use std::io::{Error, ErrorKind, Result, Write};
6
7use crate::types::{TypeID, UNALIGNED_CHUNKS};
8
9pub enum Chunk {
10    Envelope {
11        envelope_id: TypeID,
12        id: TypeID,
13        chunks: Vec<Chunk>,
14        little_endian: bool,
15        aligned: bool
16    },
17    Data {
18        id: TypeID,
19        data: Vec<u8>,
20        little_endian: bool
21    },
22}
23impl Chunk {
24    pub fn from_data(data: &[u8], little_endian: Option<bool>) -> Result<Self> {
25        Chunk::from_data_full(data, 0, data.len(), little_endian, None)
26    }
27
28    pub fn from_data_with_size(data: &[u8], size_override: usize, little_endian: Option<bool>) -> Result<Self> {
29        Chunk::from_data_full(data, 0, data.len(), little_endian, Some(size_override))
30    }
31
32    pub fn is_little_endian(&self) -> bool {
33        match self {
34            Chunk::Envelope { little_endian, .. } => *little_endian,
35            Chunk::Data { little_endian, .. } => *little_endian
36        }
37    }
38
39    fn from_data_full(data: &[u8], index: usize, last_index: usize, little_endian: Option<bool>,
40                      size_override: Option<usize>) -> Result<Self> {
41        if index + 8 > last_index {
42            return Err(Error::new(ErrorKind::InvalidData, "invalid data"));
43        }
44        let mut id = Self::chunk_id(&data, index, little_endian.unwrap_or(false));
45        let little_endian = if let Some(v) = little_endian {
46            v
47        } else {
48            if id.is_envelope() {
49                false
50            } else if id.is_le_envelope() {
51                id = id.reverse(); // we didn't know this was little-endian when we read it
52                true
53            } else {
54                return Err(Error::new(ErrorKind::InvalidInput, "data chunk needs 'little_endian' setting"));
55            }
56        };
57
58        let size = size_override.unwrap_or_else(
59            || Self::chunk_size(&data, index+4, little_endian));
60        //println!("chunk '{}' len {} at {} env {}", id, size, index, id.is_envelope());
61        if index + 8 + size > last_index {
62            return Err(Error::new(ErrorKind::InvalidData, "invalid data"));
63        }
64        if id.is_envelope() {
65            let aligned = UNALIGNED_CHUNKS.contains(&&id);
66            if size < 4 {
67                return Err(Error::new(ErrorKind::InvalidData, "invalid data"));
68            }
69            let data_id = Self::chunk_id(&data, index+8, little_endian);
70            let mut i = index + 12;
71            //println!("size {}", size);
72            let mut chunks = Vec::new();
73            while i < index + 8 + size {
74                let chunk = Self::from_data_full(&data, i, index+8+size, Some(little_endian), None)?;
75                i += chunk.size();
76                if aligned && i % 2 != 0 {
77                    i += 1;
78                }
79                chunks.push(chunk);
80            }
81            Ok(Chunk::Envelope{ envelope_id: id, id: data_id, chunks, little_endian, aligned })
82        } else {
83            Ok(Chunk::Data{ id, data: data[index+8..index+8+size].to_vec(), little_endian })
84        }
85    }
86
87    fn chunk_id(data: &[u8], index: usize, little_endian: bool) -> TypeID {
88        // SAFETY: ok because caller checks the length
89        let ptr = data[index..].as_ptr() as *const [u8; 4];
90        let arr = unsafe { &*ptr };
91
92        TypeID::from_data(arr, little_endian)
93    }
94
95    fn chunk_size(data: &[u8], index: usize, little_endian: bool) -> usize {
96        // SAFETY: ok because caller checks the length
97        let ptr = data[index..].as_ptr() as *const [u8; 4];
98        let arr = unsafe { &*ptr };
99        if little_endian {
100            u32::from_le_bytes(*arr) as usize
101        } else {
102            u32::from_be_bytes(*arr) as usize
103        }
104    }
105
106    pub fn create(envelope_id: TypeID, id: TypeID, little_endian: bool) -> Self {
107        let aligned = UNALIGNED_CHUNKS.contains(&&id);
108        Chunk::Envelope{ envelope_id, id, chunks: Vec::new(), little_endian, aligned }
109    }
110
111    /*
112    pub fn append_data(&mut self, id: TypeID, data: &[u8]) {
113        if let &mut Chunk::Envelope{ ref mut chunks, little_endian, .. } = self {
114            chunks.push(Chunk::Data{ id, data, little_endian });
115        } else {
116            panic!("Cannot add nested chunks to a data chunk");
117        }
118    }
119     */
120
121    pub fn append_chunk(&mut self, chunk: Chunk) {
122        if let &mut Chunk::Envelope{ ref mut chunks, .. } = self {
123            chunks.push(chunk);
124        } else {
125            panic!("Cannot add nested chunks to a data chunk");
126        }
127    }
128
129    fn write_type_id<W: Write>(id: &TypeID, w: &mut W, little_endian: bool) -> Result<()> {
130        let mut data = [0u8;4];
131        id.to_data(&mut data, little_endian);
132        w.write_all(&data)
133    }
134
135    fn write_u32<W: Write>(value: u32, w: &mut W, little_endian: bool) -> Result<()> {
136        let data = if little_endian {
137            value.to_le_bytes()
138        } else {
139            value.to_be_bytes()
140        };
141        w.write_all(&data)
142    }
143
144    pub fn write<W: Write>(&self, w: &mut W) -> Result<()> {
145        match self {
146            Chunk::Envelope{ envelope_id, id, chunks, little_endian, aligned } => {
147                Self::write_type_id(envelope_id, w, *little_endian)?;
148                let size = self.size() - 8;
149                Self::write_u32(size as u32, w, *little_endian)?;
150                Self::write_type_id(id, w, *little_endian)?;
151                for chunk in chunks {
152                    chunk.write(w)?;
153                }
154                if *aligned && size % 2 != 0 {
155                    w.write_all(&[0u8])?;
156                }
157            },
158            Chunk::Data{ id, data, little_endian } => {
159                Self::write_type_id(id, w, *little_endian)?;
160                let size = self.size() - 8;
161                Self::write_u32(size as u32, w, *little_endian)?;
162                w.write_all(data)?;
163                // TODO: data chunks know nothing of alignment. What happens in sounddiver
164                //       when data chunk is of odd size?
165                if size % 2 != 0 {
166                    w.write_all(&[0u8])?;
167                }
168            }
169        }
170        Ok(())
171    }
172
173    fn size(&self) -> usize {
174        match self {
175            &Chunk::Envelope{ ref chunks, aligned, .. } => {
176                let mut size = 12;
177                for chunk in chunks {
178                    size += chunk.size();
179                    if aligned && size % 2 != 0 {
180                        size += 1;
181                    }
182                }
183                size
184            },
185            &Chunk::Data{ id:_, ref data, little_endian:_ } => 8 + data.len(),
186        }
187    }
188
189    pub fn has_envelope_type(&self, envelope_type_id: TypeID, type_id: TypeID) -> bool {
190        match self {
191            &Chunk::Envelope{ envelope_id, id, .. } =>
192                envelope_type_id == envelope_id && type_id == id,
193            _ => false,
194        }
195    }
196
197    pub fn has_data_type(&self, type_id: TypeID) -> bool {
198        match self {
199            &Chunk::Data{ id, .. } => type_id == id,
200            _ => false,
201        }
202    }
203
204
205    pub fn data_chunks(&self) -> Vec<(TypeID,&Vec<u8>)> {
206        let mut vec = Vec::new();
207        match self {
208            &Chunk::Envelope{ ref chunks, .. } => {
209                for chunk in chunks {
210                    match chunk {
211                        &Chunk::Data{ id, ref data, little_endian:_ } => vec.push((id, data)),
212                        _ => (),
213                    }
214                }
215            },
216            _ => (),
217        }
218        vec
219    }
220
221    pub fn all_chunks(&self) -> Vec<(TypeID,&Chunk)> {
222        let mut vec = Vec::new();
223        match self {
224            &Chunk::Envelope{ ref chunks, .. } => {
225                for chunk in chunks {
226                    match chunk {
227                        &Chunk::Data{ id, .. } => vec.push((id, chunk)),
228                        &Chunk::Envelope{ id, .. } => vec.push((id, chunk)),
229                    }
230                }
231            },
232            _ => (),
233        }
234        vec
235    }
236}
237
238impl std::fmt::Debug for Chunk {
239    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
240        match self {
241            Chunk::Envelope { envelope_id, id, chunks, little_endian, aligned } => {
242                f.debug_struct("Chunk::Envelope")
243                    .field("envelope", &envelope_id)
244                    .field("id", &id)
245                    .field("chunks.len", &chunks.len())
246                    .field("little_endian", &little_endian)
247                    .field("aligned", &aligned)
248                    .finish()
249            }
250            Chunk::Data { id, data, little_endian } => {
251                f.debug_struct("Chunk::Data")
252                    .field("id", &id)
253                    .field("data.len", &data.len())
254                    .field("little_endian", &little_endian)
255                    .finish()
256            }
257        }
258    }
259}