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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! Parsing functionality from bytes for raw types.
//! Note: this is only enabled with the `parse` feature
extern crate alloc;

mod archive_extra_data_record;
mod central_directory_header;
mod data_descriptor;
mod digital_signature;
mod end_of_central_directory;
mod extensible_data;
mod local_file_header;
mod zip64_end_of_central_directory;

#[cfg(feature = "std")] use std::error::Error;

#[derive(Debug, PartialEq)]
pub enum FixedSizeError {
    /// the buffer has not enough bytes to parse this record.
    /// An exact size is known at should be provided.
    /// It's okay to provide a larger buffer.
    UnsufficientExactBytes(usize),
}

#[derive(Debug, PartialEq)]
pub enum DynamicSizeError {
    /// needed to get the size necessary before parsing. as we cannot undo the
    /// advancing on a Buf. with `std` its possible to get around this
    /// limitation by using bytes::chunks_vectored, however in no_std case we
    /// need this error type.
    /// parameters returns how many bytes AT LEAST need to be contiguous.
    NotContiguous(usize),
    /// see [`FixedSizeError::UnsufficientExactBytes`]
    ///
    /// [`FixedSizeError::UnsufficientExactBytes`]
    /// FixedSizeError::UnsufficientExactBytes
    UnsufficientExactBytes(usize),
    /// This means that the buffer has not enough bytes to read the length of
    /// the dynamic part (e.g. there are not enough bytes to parse the fixed
    /// part). Please catch this, and increase the buffer.
    /// The buffer needs at least this size, hoewever it will prob fail with
    /// a [`Self::UnsufficientExactBytes`] if you just provide `n` bytes and not
    /// enough for the dynmapic part.
    ///
    /// [`Self::UnsufficientExactBytes`] Self::UnsufficientExactBytes
    UnsufficientAtLeastBytes(usize),
}

/// for some records, the zip convention choose to allow specifying the total
/// length in its fixed field, e.g. [`Zip64EndOfCentralDirectory`]. Those fields
/// could specify a value < its fixed size, e.g. 0 which would be totally
/// invalid and unexpected, as it would mean that we shouldn't even concider its
/// signature at this point.
///
/// [`Zip64EndOfCentralDirectory`]: super::Zip64EndOfCentralDirectory
#[derive(Debug, PartialEq)]
pub enum DynamicSizeTotalSizeError {
    Dynamic(DynamicSizeError),
    TotalSizeInFieldIsInvalid,
}

pub(super) fn preview_u16_from_buf(buf: &[u8], pos: usize) -> Option<u16> {
    buf.get(pos..pos + 2)
        .map(|src| unsafe { u16::from_le_bytes(*(src as *const _ as *const [_; 2])) })
}

// checks the length and returns and error in case it is not enough
pub(super) fn validate_length<E, T: Fn(usize) -> E>(have: usize, want: usize, e: T) -> Result<(), E> {
    if have < want {
        return Err(e(want - have));
    }
    Ok(())
}

pub trait Parse {
    type Error;

    /// parsing this Type from a buf, returning either the Type or an Error.
    /// implementations must not modify the buf in case of an error.
    fn from_buf<T: bytes::Buf>(buf: &mut T) -> Result<Self, Self::Error>
    where
        Self: Sized;

    /// writing self to a buffer.
    /// implementation must not modify the buf in case of an error.
    //TODO: evaluate using a different error type, because its unusual that
    // UnsufficientAtLeastBytes is thrown here.
    fn to_buf<T: bytes::BufMut>(&self, buf: &mut T) -> Result<(), Self::Error>;
}

pub trait ParseExtend {
    type Fixed;
    type Error;

    /// parsing this Type from a buf and a fixed part, returning either the Type
    /// or an Error. implementations must not modify the buf in case of an
    /// error and return the fixed
    fn from_buf_fixed<T: bytes::Buf>(buf: &mut T, fixed: Self::Fixed) -> Result<Self, (Self::Error, Self::Fixed)>
    where
        Self: Sized;
}

impl FixedSizeError {
    pub(super) fn in_dynamic(self) -> DynamicSizeError {
        match self {
            // Nature of this Error type is that there is some additional bytes, so we can only make that AtLeast
            FixedSizeError::UnsufficientExactBytes(b) => DynamicSizeError::UnsufficientAtLeastBytes(b),
        }
    }
}

impl From<DynamicSizeError> for DynamicSizeTotalSizeError {
    fn from(value: DynamicSizeError) -> Self { DynamicSizeTotalSizeError::Dynamic(value) }
}

impl core::fmt::Display for FixedSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            FixedSizeError::UnsufficientExactBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have {n} additional bytes to parse"
            ),
        }
    }
}

impl core::fmt::Display for DynamicSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            DynamicSizeError::NotContiguous(n) => write!(
                f,
                "Bytes support data not beeing Continous, however to peek data its required that the data is within \
                 the first chunk of data. You need to reorganize the underlying data structure so that the first {n} \
                 bytes are within the first chun."
            ),
            DynamicSizeError::UnsufficientExactBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have {n} additional bytes to parse"
            ),
            DynamicSizeError::UnsufficientAtLeastBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have at least {n} additional bytes to \
                 parse, probably even more bytes are needed. An exact estimation cannot be made yet"
            ),
        }
    }
}

impl core::fmt::Display for DynamicSizeTotalSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            DynamicSizeTotalSizeError::Dynamic(e) => write!(f, "{e}"),
            DynamicSizeTotalSizeError::TotalSizeInFieldIsInvalid => write!(
                f,
                "The Record contains an corrupt field stating the total size is lower than the minimum size"
            ),
        }
    }
}

#[cfg(feature = "std")]
impl Error for FixedSizeError {}
#[cfg(feature = "std")]
impl Error for DynamicSizeError {}
#[cfg(feature = "std")]
impl Error for DynamicSizeTotalSizeError {}

/// finds next occurence of a signature in a buffer, in case signature is
/// le-encoded
#[inline(always)]
#[allow(clippy::manual_find)] //performance improvement
pub fn find_next_signature(buf: &[u8], signature: [u8; 4]) -> Option<usize> {
    for i in 0..buf.len() - 4 {
        if buf[i] == signature[0]
            && buf[i + 1] == signature[1]
            && buf[i + 2] == signature[2]
            && buf[i + 3] == signature[3]
        {
            return Some(i);
        }
    }
    None
    /*
    (0..buf.len() - 4).find(|&i| {
        buf[i] == signature[0] && buf[i + 1] == signature[1] && buf[i + 2] == signature[2] && buf[i + 3] == signature[3]
    })
    */
}

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

    #[test]
    fn display_dont_panic() {
        let mut buf = vec![0; 20000];
        use std::io::Write;
        write!(buf, "{}", FixedSizeError::UnsufficientExactBytes(1)).unwrap();
        write!(buf, "{}", DynamicSizeError::NotContiguous(2)).unwrap();
        write!(buf, "{}", DynamicSizeError::UnsufficientExactBytes(3)).unwrap();
        write!(buf, "{}", DynamicSizeError::UnsufficientAtLeastBytes(4)).unwrap();
        write!(
            buf,
            "{}",
            DynamicSizeTotalSizeError::Dynamic(DynamicSizeError::NotContiguous(5))
        )
        .unwrap();
        write!(buf, "{}", DynamicSizeTotalSizeError::TotalSizeInFieldIsInvalid).unwrap();
    }
}