libpna/
chunk.rs

1mod crc;
2mod read;
3mod traits;
4mod types;
5mod write;
6
7use self::crc::Crc32;
8pub(crate) use self::{read::*, write::*};
9pub use self::{traits::*, types::*};
10use std::{
11    borrow::Cow,
12    io::{self, prelude::*},
13    mem,
14};
15
16/// Minimum required size in bytes to represent [`Chunk`].
17/// length: 4 bytes + chunk type: 4 bytes + data: 0 bytes + crc: 4 bytes
18pub const MIN_CHUNK_BYTES_SIZE: usize =
19    mem::size_of::<u32>() + mem::size_of::<ChunkType>() + mem::size_of::<u32>();
20
21/// Maximum length of chunk body in bytes.
22pub(crate) const MAX_CHUNK_DATA_LENGTH: usize = u32::MAX as usize;
23
24pub(crate) trait ChunkExt: Chunk {
25    /// size of chunk in bytes
26    #[inline]
27    fn bytes_len(&self) -> usize {
28        MIN_CHUNK_BYTES_SIZE + self.data().len()
29    }
30
31    /// check the chunk type is stream chunk
32    #[inline]
33    fn is_stream_chunk(&self) -> bool {
34        self.ty() == ChunkType::FDAT || self.ty() == ChunkType::SDAT
35    }
36
37    #[inline]
38    fn write_chunk_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
39        writer.write_all(&self.length().to_be_bytes())?;
40        writer.write_all(&self.ty().0)?;
41        writer.write_all(self.data())?;
42        writer.write_all(&self.crc().to_be_bytes())?;
43        Ok(self.bytes_len())
44    }
45
46    /// Convert the provided `Chunk` instance into a `Vec<u8>`.
47    ///
48    /// # Returns
49    ///
50    /// A `Vec<u8>` containing the converted `Chunk` data.
51    #[allow(dead_code)]
52    #[inline]
53    fn to_bytes(&self) -> Vec<u8> {
54        let mut vec = Vec::with_capacity(self.bytes_len());
55        vec.extend_from_slice(&self.length().to_be_bytes());
56        vec.extend_from_slice(&self.ty().0);
57        vec.extend_from_slice(self.data());
58        vec.extend_from_slice(&self.crc().to_be_bytes());
59        vec
60    }
61}
62
63impl<T> ChunkExt for T where T: Chunk {}
64
65/// Represents a raw chunk
66#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
67pub struct RawChunk<D = Vec<u8>> {
68    pub(crate) length: u32,
69    pub(crate) ty: ChunkType,
70    pub(crate) data: D,
71    pub(crate) crc: u32,
72}
73
74impl<D> From<(ChunkType, D)> for RawChunk<D>
75where
76    (ChunkType, D): Chunk,
77{
78    #[inline]
79    fn from(value: (ChunkType, D)) -> Self {
80        Self {
81            length: value.length(),
82            crc: value.crc(),
83            ty: value.0,
84            data: value.1,
85        }
86    }
87}
88
89impl<'d> RawChunk<&'d [u8]> {
90    pub(crate) fn from_slice(ty: ChunkType, data: &'d [u8]) -> Self {
91        let chunk = (ty, data);
92        Self {
93            length: chunk.length(),
94            crc: chunk.crc(),
95            ty,
96            data,
97        }
98    }
99}
100
101impl<'a> From<RawChunk<Cow<'a, [u8]>>> for RawChunk<Vec<u8>> {
102    #[inline]
103    fn from(value: RawChunk<Cow<'a, [u8]>>) -> Self {
104        Self {
105            length: value.length,
106            ty: value.ty,
107            data: value.data.into(),
108            crc: value.crc,
109        }
110    }
111}
112
113impl<'a> From<RawChunk<&'a [u8]>> for RawChunk<Vec<u8>> {
114    #[inline]
115    fn from(value: RawChunk<&'a [u8]>) -> Self {
116        Self {
117            length: value.length,
118            ty: value.ty,
119            data: value.data.into(),
120            crc: value.crc,
121        }
122    }
123}
124
125impl From<RawChunk<Vec<u8>>> for RawChunk<Cow<'_, [u8]>> {
126    #[inline]
127    fn from(value: RawChunk<Vec<u8>>) -> Self {
128        Self {
129            length: value.length,
130            ty: value.ty,
131            data: Cow::Owned(value.data),
132            crc: value.crc,
133        }
134    }
135}
136
137impl<'a> From<RawChunk<&'a [u8]>> for RawChunk<Cow<'a, [u8]>> {
138    #[inline]
139    fn from(value: RawChunk<&'a [u8]>) -> Self {
140        Self {
141            length: value.length,
142            ty: value.ty,
143            data: Cow::Borrowed(value.data),
144            crc: value.crc,
145        }
146    }
147}
148
149impl<D> RawChunk<D>
150where
151    Self: Chunk,
152{
153    #[inline]
154    pub(crate) fn as_ref(&self) -> RawChunk<&[u8]> {
155        RawChunk {
156            length: self.length,
157            ty: self.ty,
158            data: self.data(),
159            crc: self.crc,
160        }
161    }
162}
163
164impl<T: AsRef<[u8]>> Chunk for RawChunk<T> {
165    #[inline]
166    fn length(&self) -> u32 {
167        self.length
168    }
169
170    #[inline]
171    fn ty(&self) -> ChunkType {
172        self.ty
173    }
174
175    #[inline]
176    fn data(&self) -> &[u8] {
177        self.data.as_ref()
178    }
179
180    #[inline]
181    fn crc(&self) -> u32 {
182        self.crc
183    }
184}
185
186impl RawChunk {
187    /// Create a new [`RawChunk`] from given [`ChunkType`] and bytes.
188    ///
189    /// # Examples
190    /// ```
191    /// use libpna::{prelude::*, ChunkType, RawChunk};
192    ///
193    /// let data = [0xAA, 0xBB, 0xCC, 0xDD];
194    /// let chunk = RawChunk::from_data(ChunkType::FDAT, data);
195    ///
196    /// assert_eq!(chunk.length(), 4);
197    /// assert_eq!(chunk.ty(), ChunkType::FDAT);
198    /// assert_eq!(chunk.data(), &[0xAA, 0xBB, 0xCC, 0xDD]);
199    /// assert_eq!(chunk.crc(), 1207118608);
200    /// ```
201    #[inline]
202    pub fn from_data<T: Into<Vec<u8>>>(ty: ChunkType, data: T) -> Self {
203        #[inline]
204        fn inner(ty: ChunkType, data: Vec<u8>) -> RawChunk {
205            let chunk = (ty, &data[..]);
206            RawChunk {
207                length: chunk.length(),
208                crc: chunk.crc(),
209                ty,
210                data,
211            }
212        }
213        inner(ty, data.into())
214    }
215}
216
217impl<T: AsRef<[u8]>> Chunk for (ChunkType, T) {
218    #[inline]
219    fn ty(&self) -> ChunkType {
220        self.0
221    }
222
223    #[inline]
224    fn data(&self) -> &[u8] {
225        self.1.as_ref()
226    }
227}
228
229impl<T: Chunk> Chunk for &T {
230    #[inline]
231    fn ty(&self) -> ChunkType {
232        T::ty(*self)
233    }
234
235    #[inline]
236    fn data(&self) -> &[u8] {
237        T::data(*self)
238    }
239}
240
241impl<T: Chunk> Chunk for &mut T {
242    #[inline]
243    fn ty(&self) -> ChunkType {
244        T::ty(*self)
245    }
246
247    #[inline]
248    fn data(&self) -> &[u8] {
249        T::data(*self)
250    }
251}
252
253#[inline]
254pub(crate) fn chunk_data_split(
255    ty: ChunkType,
256    data: &[u8],
257    mid: usize,
258) -> (RawChunk<&[u8]>, Option<RawChunk<&[u8]>>) {
259    if let Some((first, last)) = data.split_at_checked(mid) {
260        if last.is_empty() {
261            (RawChunk::from_slice(ty, first), None)
262        } else {
263            (
264                RawChunk::from_slice(ty, first),
265                Some(RawChunk::from_slice(ty, last)),
266            )
267        }
268    } else {
269        (RawChunk::from_slice(ty, data), None)
270    }
271}
272
273/// Read archive as chunks from given reader.
274///
275/// Reads a PNA archive from the given reader and returns an iterator of chunks.
276///
277/// # Errors
278/// Returns error if it is not PNA file.
279///
280/// # Example
281///
282/// ```no_run
283/// # use std::{io, fs};
284/// use libpna::{prelude::*, read_as_chunks};
285/// # fn main() -> io::Result<()> {
286/// let archive = fs::File::open("foo.pna")?;
287/// for chunk in read_as_chunks(archive)? {
288///     let chunk = chunk?;
289///     println!(
290///         "chunk type: {}, chunk data size: {}",
291///         chunk.ty(),
292///         chunk.length()
293///     );
294/// }
295/// # Ok(())
296/// # }
297/// ```
298#[inline]
299pub fn read_as_chunks<R: Read>(
300    mut archive: R,
301) -> io::Result<impl Iterator<Item = io::Result<impl Chunk>>> {
302    struct Chunks<R> {
303        reader: ChunkReader<R>,
304        eoa: bool,
305    }
306    impl<R: Read> Iterator for Chunks<R> {
307        type Item = io::Result<RawChunk>;
308        #[inline]
309        fn next(&mut self) -> Option<Self::Item> {
310            if self.eoa {
311                return None;
312            }
313            Some(self.reader.read_chunk().inspect(|chunk| {
314                self.eoa = chunk.ty() == ChunkType::AEND;
315            }))
316        }
317    }
318    crate::archive::read_pna_header(&mut archive)?;
319
320    Ok(Chunks {
321        reader: ChunkReader::from(archive),
322        eoa: false,
323    })
324}
325
326/// Read archive as chunks from given bytes.
327///
328/// Reads a PNA archive from the given byte slice and returns an iterator of chunks.
329///
330/// # Errors
331/// Returns error if it is not PNA file.
332///
333/// # Example
334///
335/// ```
336/// # use std::{io, fs};
337/// use libpna::{prelude::*, read_chunks_from_slice};
338/// # fn main() -> io::Result<()> {
339/// let bytes = include_bytes!("../../resources/test/zstd.pna");
340/// for chunk in read_chunks_from_slice(bytes)? {
341///     let chunk = chunk?;
342///     println!(
343///         "chunk type: {}, chunk data size: {}",
344///         chunk.ty(),
345///         chunk.length()
346///     );
347/// }
348/// # Ok(())
349/// # }
350/// ```
351#[inline]
352pub fn read_chunks_from_slice(
353    archive: &[u8],
354) -> io::Result<impl Iterator<Item = io::Result<impl Chunk + '_>>> {
355    struct Chunks<'a> {
356        reader: &'a [u8],
357        eoa: bool,
358    }
359    impl<'a> Iterator for Chunks<'a> {
360        type Item = io::Result<RawChunk<&'a [u8]>>;
361        #[inline]
362        fn next(&mut self) -> Option<Self::Item> {
363            if self.eoa {
364                return None;
365            }
366            Some(read_chunk_from_slice(self.reader).map(|(chunk, bytes)| {
367                self.eoa = chunk.ty() == ChunkType::AEND;
368                self.reader = bytes;
369                chunk
370            }))
371        }
372    }
373    let archive = crate::archive::read_header_from_slice(archive)?;
374
375    Ok(Chunks {
376        reader: archive,
377        eoa: false,
378    })
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
385    use wasm_bindgen_test::wasm_bindgen_test as test;
386
387    #[test]
388    fn chunk_trait_bounds() {
389        fn check_impl<T: Chunk>() {}
390        check_impl::<RawChunk<Vec<u8>>>();
391        check_impl::<RawChunk<Cow<[u8]>>>();
392        check_impl::<RawChunk<&[u8]>>();
393        check_impl::<RawChunk<[u8; 1]>>();
394    }
395
396    #[test]
397    fn to_bytes() {
398        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
399        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
400
401        let bytes = chunk.to_bytes();
402
403        assert_eq!(
404            bytes,
405            vec![
406                0x00, 0x00, 0x00, 0x04, // chunk length (4)
407                0x46, 0x44, 0x41, 0x54, // chunk type ("FDAT")
408                0xAA, 0xBB, 0xCC, 0xDD, // data bytes
409                0x47, 0xf3, 0x2b, 0x10, // CRC32 (calculated from chunk type and data)
410            ]
411        );
412    }
413
414    #[test]
415    fn data_split_at_zero() {
416        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
417        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
418        assert_eq!(
419            chunk_data_split(chunk.ty, chunk.data(), 0),
420            (
421                RawChunk::from_slice(ChunkType::FDAT, &[]),
422                Some(RawChunk::from_slice(
423                    ChunkType::FDAT,
424                    &[0xAA, 0xBB, 0xCC, 0xDD]
425                )),
426            )
427        )
428    }
429
430    #[test]
431    fn data_split_at_middle() {
432        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
433        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
434        assert_eq!(
435            chunk_data_split(chunk.ty, chunk.data(), 2),
436            (
437                RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB]),
438                Some(RawChunk::from_slice(ChunkType::FDAT, &[0xCC, 0xDD])),
439            )
440        )
441    }
442
443    #[test]
444    fn data_split_at_just() {
445        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
446        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
447        assert_eq!(
448            chunk_data_split(chunk.ty, chunk.data(), 4),
449            (
450                RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB, 0xCC, 0xDD]),
451                None,
452            )
453        )
454    }
455
456    #[test]
457    fn data_split_at_over() {
458        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
459        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
460        assert_eq!(
461            chunk_data_split(chunk.ty, chunk.data(), 5),
462            (
463                RawChunk::from_slice(ChunkType::FDAT, &[0xAA, 0xBB, 0xCC, 0xDD]),
464                None,
465            )
466        )
467    }
468}