Skip to main content

sit/
verify.rs

1use std::{
2    io::{self, Read as _, Seek as _},
3    rc::Rc,
4};
5
6use binrw::BinReaderExt;
7use crc::{self, Digest};
8
9use crate::{
10    Entry, Error,
11    algos::EntryReader,
12    structs::{
13        v1,
14        v5::{self, EntryBinReadArgs},
15    },
16};
17
18const VERIFICATION_CHUNK_SIZE: usize = 1 << 20 /* bytes => 1 Mb */;
19const CRC16: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_ARC);
20
21pub struct VerifyingIterator<R: io::Read + io::Seek> {
22    pub(crate) next_offset: u64,
23    pub(crate) stack: Vec<u32>,
24    pub(crate) v1: bool,
25    pub(crate) _next_file_index: usize,
26
27    pub(crate) reader: Rc<R>,
28}
29
30impl<R: io::Read + io::Seek> Iterator for VerifyingIterator<R> {
31    type Item = Entry;
32
33    fn next(&mut self) -> Option<Self::Item> {
34        let reader = unsafe { Rc::get_mut_unchecked(&mut self.reader) };
35
36        match self.stack.last_mut() {
37            None => return None,
38            Some(0) => {
39                self.stack.pop();
40
41                return if self.stack.is_empty() {
42                    None
43                } else {
44                    Some(Entry::DirectoryEnd(reader.stream_position().unwrap()))
45                };
46            }
47            Some(d) => *d -= 1,
48        }
49
50        let Ok(entry_offset) = reader.seek(io::SeekFrom::Start(self.next_offset)) else {
51            log::warn!("Failed seeking to next archive entry");
52            return None;
53        };
54
55        if self.v1 {
56            let Ok(entry) = reader.read_be::<v1::Entry>() else {
57                return None;
58            };
59
60            let Ok(payload_offset) = reader.stream_position() else {
61                return None;
62            };
63
64            match entry {
65                v1::Entry::Directory(dir) => {
66                    self.next_offset = payload_offset;
67                    self.stack.push(u32::MAX);
68
69                    Some(Entry::Directory(dir.into()))
70                }
71                v1::Entry::DirectoryEnd => {
72                    self.next_offset = payload_offset;
73                    self.stack.pop();
74
75                    Some(Entry::DirectoryEnd(payload_offset))
76                }
77                v1::Entry::File(file) => {
78                    self.next_offset = payload_offset
79                        + file.data_compressed_size as u64
80                        + file.rsrc_compressed_size as u64;
81
82                    Some(Entry::File(file.into()))
83                }
84            }
85        } else {
86            let Ok(entry) = reader.read_be_args::<v5::Entry>(
87                EntryBinReadArgs::builder().offset(entry_offset).finalize(),
88            ) else {
89                return None;
90            };
91
92            let Ok(payload_offset) = reader.stream_position() else {
93                return None;
94            };
95
96            match entry {
97                v5::Entry::Directory(dir) => {
98                    if dir.marks_end() {
99                        self.next_offset = payload_offset;
100                        self.next();
101                    }
102
103                    self.stack.push(dir.child_count);
104                    self.next_offset = dir.first_child_offset;
105
106                    Some(Entry::Directory(dir.into()))
107                }
108                v5::Entry::File(mut file) => {
109                    file.payload_offset = reader.stream_position().unwrap();
110                    self.next_offset = file.next_entry_offset as u64;
111
112                    Some(Entry::File(file.into()))
113                }
114            }
115        }
116    }
117}
118
119pub struct VerifyingEntryReader<'a, R: io::Read + io::Seek> {
120    inner: EntryReader<'a, R>,
121    checksum: u16,
122    digest: Digest<'a, u16>,
123    skip: bool,
124    empty: bool,
125    invalid: bool,
126}
127
128impl<'a, R: io::Seek + io::Read> VerifyingEntryReader<'a, R> {
129    pub(crate) fn new(mut inner: EntryReader<'a, R>, checksum: u16, skip: bool) -> Self {
130        let empty_stream = inner.stream_len().unwrap() == 0;
131
132        Self {
133            invalid: false,
134            empty: empty_stream,
135            skip,
136            inner,
137            checksum,
138            digest: CRC16.digest(),
139        }
140    }
141}
142
143impl<'a, R: io::Read + io::Seek> VerifyingEntryReader<'a, R> {
144    /// Read all available data and verify it's contents using the provided checksum
145    pub fn slurp(mut self) -> Result<(), Error> {
146        let mut chunk = vec![0u8; VERIFICATION_CHUNK_SIZE];
147        loop {
148            match self.read(&mut chunk)? {
149                s if s < VERIFICATION_CHUNK_SIZE => {
150                    log::info!("slurpded: {s}");
151                    return Ok(());
152                }
153                _ => {
154                    continue;
155                }
156            }
157        }
158    }
159}
160
161impl<'a, R: io::Read + io::Seek> io::Read for VerifyingEntryReader<'a, R> {
162    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
163        if self.empty {
164            return Ok(0);
165        }
166
167        if self.invalid {
168            return Err(io::Error::other(Error::ChecksumMismatch(
169                crate::error::ChecksumLocation::DataStream,
170            )));
171        }
172
173        let size = self.inner.read(buf)?;
174        if self.skip {
175            if self.inner.ended()?
176                && let EntryReader::Arsenic { reader, .. } = &mut self.inner
177            {
178                if !reader.is_checksum_valid() {
179                    self.invalid = true;
180                    return Err(io::Error::other(Error::ChecksumMismatch(
181                        crate::error::ChecksumLocation::DataStream,
182                    )));
183                } else {
184                    self.empty = true;
185                }
186            }
187
188            return Ok(size);
189        }
190        self.digest.update(&buf[0..size]);
191
192        if self.inner.ended()? {
193            self.digest.update(&[
194                (self.checksum & 0xFF) as u8,
195                ((self.checksum >> 8) & 0xFF) as u8,
196            ]);
197
198            log::info!(
199                "Checking CRC16 checksum 0x{:04x} against 0x{:04x}",
200                self.checksum,
201                self.digest.clone().finalize(),
202            );
203
204            if self.digest.clone().finalize() != 0 {
205                log::info!("Checksum is invalid");
206                self.invalid = true;
207                return Err(io::Error::other(Error::ChecksumMismatch(
208                    crate::error::ChecksumLocation::DataStream,
209                )));
210            }
211
212            log::info!("Checksum is valid");
213            self.skip = true;
214        }
215
216        Ok(size)
217    }
218}