1use crate::pb::{FlatUnixFs, PBLink, RangeLinks, UnixFsType};
2use core::convert::TryFrom;
3use core::fmt;
4use core::ops::Range;
5
6use crate::file::{FileError, FileReadFailed, Metadata, UnwrapBorrowedExt};
7
8pub struct FileReader<'a> {
20 offset: u64,
21 end: Ending,
22 links: Vec<PBLink<'a>>,
23 data: &'a [u8],
24 blocksizes: Vec<u64>,
25 metadata: Metadata,
26 file_size: u64,
27}
28
29impl AsRef<Metadata> for FileReader<'_> {
30 fn as_ref(&self) -> &Metadata {
31 &self.metadata
32 }
33}
34
35#[derive(Debug)]
38enum Ending {
39 TreeCoverage(u64),
41 Chunk(u64),
43}
44
45impl Ending {
46 fn check_is_suitable_next(&self, offset: u64, next: &Range<u64>) -> Result<(), FileError> {
48 match self {
49 Ending::TreeCoverage(cover_end) if next.start <= offset && &next.end > cover_end => {
50 Err(FileError::TreeExpandsOnLinks)
53 }
54 Ending::TreeCoverage(cover_end) if &next.start < cover_end && &next.end > cover_end => {
55 Err(FileError::TreeOverlapsBetweenLinks)
62 }
63 Ending::TreeCoverage(_) if next.start < offset => Err(FileError::EarlierLink),
64 Ending::Chunk(chunk_end) if &next.start != chunk_end => {
65 Err(FileError::TreeJumpsBetweenLinks)
68 }
69 _ => Ok(()),
70 }
71 }
72}
73
74impl<'a> FileReader<'a> {
75 pub fn from_block(data: &'a [u8]) -> Result<Self, FileReadFailed> {
77 let inner = FlatUnixFs::try_from(data)?;
78 let metadata = Metadata::from(&inner.data);
79 Self::from_parts(inner, 0, metadata)
80 }
81
82 pub(crate) fn from_parsed(inner: FlatUnixFs<'a>) -> Result<Self, FileReadFailed> {
83 let metadata = Metadata::from(&inner.data);
84 Self::from_parts(inner, 0, metadata)
85 }
86
87 fn from_continued(
89 traversal: Traversal,
90 offset: u64,
91 data: &'a [u8],
92 ) -> Result<Self, FileReadFailed> {
93 let inner = FlatUnixFs::try_from(data)?;
94
95 if inner.data.mode.is_some() || inner.data.mtime.is_some() {
96 let metadata = Metadata::from(&inner.data);
97 return Err(FileError::NonRootDefinesMetadata(metadata).into());
98 }
99
100 Self::from_parts(inner, offset, traversal.metadata)
101 }
102
103 fn from_parts(
104 inner: FlatUnixFs<'a>,
105 offset: u64,
106 metadata: Metadata,
107 ) -> Result<Self, FileReadFailed> {
108 let empty_or_no_content = inner
109 .data
110 .Data
111 .as_ref()
112 .map(|cow| cow.as_ref().is_empty())
113 .unwrap_or(true);
114 let is_zero_bytes = inner.data.filesize.unwrap_or(0) == 0;
115
116 if inner.data.Type != UnixFsType::File && inner.data.Type != UnixFsType::Raw {
117 Err(FileReadFailed::UnexpectedType(inner.data.Type.into()))
118 } else if inner.links.len() != inner.data.blocksizes.len() {
119 Err(FileError::LinksAndBlocksizesMismatch.into())
120 } else if empty_or_no_content && !is_zero_bytes && inner.links.is_empty() {
121 Err(FileError::NoLinksNoContent.into())
122 } else {
123 let data = inner.data.Data.unwrap_borrowed_or_empty();
125
126 if inner.data.hashType.is_some() || inner.data.fanout.is_some() {
127 return Err(FileError::UnexpectedRawOrFileProperties {
128 hash_type: inner.data.hashType,
129 fanout: inner.data.fanout,
130 }
131 .into());
132 }
133
134 let end = if inner.links.is_empty() {
135 let filesize = inner.data.filesize.unwrap_or(data.len() as u64);
137 Ending::Chunk(offset + filesize)
138 } else {
139 match inner.data.filesize {
140 Some(filesize) => Ending::TreeCoverage(offset + filesize),
141 None => return Err(FileError::IntermediateNodeWithoutFileSize.into()),
142 }
143 };
144
145 Ok(Self {
146 offset,
147 end,
148 links: inner.links,
149 data,
150 blocksizes: inner.data.blocksizes,
151 metadata,
152 file_size: inner.data.filesize.unwrap(),
153 })
154 }
155 }
156
157 pub fn content(
160 self,
161 ) -> (
162 FileContent<'a, impl Iterator<Item = (PBLink<'a>, Range<u64>)>>,
163 Traversal,
164 ) {
165 let traversal = Traversal {
166 last_ending: self.end,
167 last_offset: self.offset,
168
169 metadata: self.metadata,
170 file_size: self.file_size,
171 };
172
173 let fc = if self.links.is_empty() {
174 FileContent::Bytes(self.data)
175 } else {
176 let zipped = self.links.into_iter().zip(self.blocksizes.into_iter());
177 FileContent::Links(RangeLinks::from_links_and_blocksizes(
178 zipped,
179 Some(self.offset),
180 ))
181 };
182
183 (fc, traversal)
184 }
185}
186
187#[derive(Debug)]
189pub struct Traversal {
190 last_ending: Ending,
191 last_offset: u64,
192 file_size: u64,
193
194 metadata: Metadata,
195}
196
197impl Traversal {
198 pub fn continue_walk<'a>(
206 self,
207 next_block: &'a [u8],
208 tree_range: &Range<u64>,
209 ) -> Result<FileReader<'a>, FileReadFailed> {
210 self.last_ending
211 .check_is_suitable_next(self.last_offset, tree_range)?;
212 FileReader::from_continued(self, tree_range.start, next_block)
213 }
214
215 pub fn file_size(&self) -> u64 {
217 self.file_size
218 }
219}
220
221impl AsRef<Metadata> for Traversal {
222 fn as_ref(&self) -> &Metadata {
223 &self.metadata
224 }
225}
226
227pub enum FileContent<'a, I>
230where
231 I: Iterator<Item = (PBLink<'a>, Range<u64>)> + 'a,
232{
233 Bytes(&'a [u8]),
236 Links(I),
240}
241
242#[cfg(test)]
243impl<'a, I> FileContent<'a, I>
244where
245 I: Iterator<Item = (PBLink<'a>, Range<u64>)>,
246{
247 pub fn unwrap_content(self) -> &'a [u8] {
249 match self {
250 FileContent::Bytes(x) => x,
251 y => panic!("Expected FileContent::Bytes, found: {:?}", y),
252 }
253 }
254}
255
256impl<'a, I> fmt::Debug for FileContent<'a, I>
257where
258 I: Iterator<Item = (PBLink<'a>, Range<u64>)>,
259{
260 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
261 match self {
262 FileContent::Bytes(bytes) => write!(fmt, "Bytes({} bytes)", bytes.len()),
263 FileContent::Links(iter) => write!(fmt, "Links({:?})", iter.size_hint()),
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::Ending;
271 use crate::file::FileError;
272
273 #[test]
274 fn collapsing_tree() {
275 Ending::TreeCoverage(100)
278 .check_is_suitable_next(0, &(0..100))
279 .unwrap();
280 Ending::TreeCoverage(100)
281 .check_is_suitable_next(0, &(0..10))
282 .unwrap();
283 Ending::TreeCoverage(100)
284 .check_is_suitable_next(0, &(0..2))
285 .unwrap();
286 Ending::Chunk(2)
287 .check_is_suitable_next(0, &(2..10))
288 .unwrap();
289 Ending::TreeCoverage(10)
290 .check_is_suitable_next(2, &(2..10))
291 .unwrap();
292 Ending::TreeCoverage(10)
293 .check_is_suitable_next(2, &(10..20))
294 .unwrap();
295 Ending::Chunk(10)
296 .check_is_suitable_next(2, &(10..100))
297 .unwrap();
298 }
299
300 #[test]
301 fn expanding_tree() {
302 let res = Ending::TreeCoverage(100).check_is_suitable_next(10, &(0..102));
303 assert!(
304 matches!(res, Err(FileError::TreeExpandsOnLinks)),
305 "{:?}",
306 res
307 );
308
309 let res = Ending::TreeCoverage(100).check_is_suitable_next(0, &(0..102));
310 assert!(
311 matches!(res, Err(FileError::TreeExpandsOnLinks)),
312 "{:?}",
313 res
314 );
315 }
316
317 #[test]
318 fn overlap() {
319 let res = Ending::TreeCoverage(100).check_is_suitable_next(10, &(88..102));
320 assert!(
321 matches!(res, Err(FileError::TreeOverlapsBetweenLinks)),
322 "{:?}",
323 res
324 );
325 }
326
327 #[test]
328 fn hole() {
329 let res = Ending::Chunk(100).check_is_suitable_next(0, &(101..105));
330 assert!(
331 matches!(res, Err(FileError::TreeJumpsBetweenLinks)),
332 "{:?}",
333 res
334 );
335 }
336
337 #[test]
338 fn wrong_next() {
339 let res = Ending::TreeCoverage(200).check_is_suitable_next(100, &(0..100));
340 assert!(matches!(res, Err(FileError::EarlierLink)), "{:?}", res);
341
342 let res = Ending::TreeCoverage(101).check_is_suitable_next(100, &(0..100));
343 assert!(matches!(res, Err(FileError::EarlierLink)), "{:?}", res);
344
345 let res = Ending::TreeCoverage(100).check_is_suitable_next(100, &(0..100));
346 assert!(matches!(res, Err(FileError::EarlierLink)), "{:?}", res);
347 }
348}