1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
mod crc;
mod read;
mod traits;
mod types;
mod write;

use self::crc::Crc32;
pub(crate) use self::{read::ChunkReader, write::ChunkWriter};
pub use self::{traits::*, types::*};
use std::{mem, ops::Deref};

pub const MIN_CHUNK_BYTES_SIZE: usize = 12;

pub(crate) trait ChunkExt: Chunk {
    /// byte size of chunk
    fn bytes_len(&self) -> usize {
        mem::align_of::<u32>() + self.ty().len() + self.data().len() + mem::align_of::<u32>()
    }

    /// check the chunk type is stream chunk
    fn is_stream_chunk(&self) -> bool {
        self.ty() == ChunkType::FDAT || self.ty() == ChunkType::SDAT
    }
}

impl<T> ChunkExt for T where T: Chunk {}

/// Represents a raw chunk
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct RawChunk {
    pub(crate) length: u32,
    pub(crate) ty: ChunkType,
    pub(crate) data: Vec<u8>,
    pub(crate) crc: u32,
}

impl RawChunk {
    pub fn from_data(ty: ChunkType, data: Vec<u8>) -> Self {
        let chunk = (ty, data);
        Self {
            length: chunk.length(),
            crc: chunk.crc(),
            ty: chunk.0,
            data: chunk.1,
        }
    }
}

impl Chunk for RawChunk {
    fn length(&self) -> u32 {
        self.length
    }

    fn ty(&self) -> ChunkType {
        self.ty
    }

    fn data(&self) -> &[u8] {
        &self.data
    }

    fn crc(&self) -> u32 {
        self.crc
    }
}

impl<T: Deref<Target = [u8]>> Chunk for (ChunkType, T) {
    fn ty(&self) -> ChunkType {
        self.0
    }

    fn data(&self) -> &[u8] {
        self.1.deref()
    }
}

/// Convert the provided `Chunk` instance into a `Vec<u8>`.
///
/// # Arguments
///
/// * `chunk` - A `Chunk` instance to be converted into a byte vector.
///
/// # Returns
///
/// A `Vec<u8>` containing the converted `Chunk` data.
///
pub(crate) fn chunk_to_bytes(chunk: impl Chunk) -> Vec<u8> {
    let mut vec = Vec::with_capacity(chunk.bytes_len());
    vec.extend_from_slice(&chunk.length().to_be_bytes());
    vec.extend_from_slice(&chunk.ty().0);
    vec.extend_from_slice(chunk.data());
    vec.extend_from_slice(&chunk.crc().to_be_bytes());
    vec
}

pub(crate) fn chunk_data_split(chunk: impl Chunk, mid: usize) -> (RawChunk, RawChunk) {
    let (first, last) = chunk.data().split_at(mid);
    (
        RawChunk::from_data(chunk.ty(), first.to_vec()),
        RawChunk::from_data(chunk.ty(), last.to_vec()),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn to_bytes() {
        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
        let chunk = RawChunk::from_data(ChunkType::FDAT, data);

        let bytes = chunk_to_bytes(chunk);

        assert_eq!(
            bytes,
            vec![
                0x00, 0x00, 0x00, 0x04, // chunk length (4)
                0x46, 0x44, 0x41, 0x54, // chunk type ("FDAT")
                0xAA, 0xBB, 0xCC, 0xDD, // data bytes
                0x47, 0xf3, 0x2b, 0x10, // CRC32 (calculated from chunk type and data)
            ]
        );
    }

    #[test]
    fn data_split_at_zero() {
        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
        assert_eq!(
            chunk_data_split(chunk, 0),
            (
                RawChunk::from_data(ChunkType::FDAT, vec![]),
                RawChunk::from_data(ChunkType::FDAT, vec![0xAA, 0xBB, 0xCC, 0xDD]),
            )
        )
    }

    #[test]
    fn data_split_at_middle() {
        let data = vec![0xAA, 0xBB, 0xCC, 0xDD];
        let chunk = RawChunk::from_data(ChunkType::FDAT, data);
        assert_eq!(
            chunk_data_split(chunk, 2),
            (
                RawChunk::from_data(ChunkType::FDAT, vec![0xAA, 0xBB]),
                RawChunk::from_data(ChunkType::FDAT, vec![0xCC, 0xDD]),
            )
        )
    }
}