Skip to main content

fuchsia_archive/
async_utf8_reader.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::error::Error;
6use fuchsia_fs::file::{AsyncGetSize, AsyncReadAt};
7use futures::lock::Mutex;
8use std::sync::Arc;
9
10/// A struct to open and read a FAR-formatted archive asynchronously.
11/// Requires that all paths are valid UTF-8.
12#[derive(Debug)]
13pub struct AsyncUtf8Reader<T>
14where
15    T: AsyncReadAt + AsyncGetSize + Unpin,
16{
17    reader: crate::async_read::AsyncReader<T>,
18}
19
20impl<T> AsyncUtf8Reader<T>
21where
22    T: AsyncReadAt + AsyncGetSize + Unpin,
23{
24    /// Create a new AsyncUtf8Reader for the provided source.
25    pub async fn new(source: T) -> Result<Self, Error> {
26        let ret = Self { reader: crate::async_read::AsyncReader::new(source).await? };
27        let () = ret.try_list().try_for_each(|r| r.map(|_| ()))?;
28        Ok(ret)
29    }
30
31    /// Return a list of the items in the archive.
32    /// Individual items will error if their paths are not valid UTF-8.
33    fn try_list(&self) -> impl ExactSizeIterator<Item = Result<crate::Utf8Entry<'_>, Error>> {
34        self.reader.list().map(|e| {
35            Ok(crate::Utf8Entry {
36                path: std::str::from_utf8(e.path).map_err(|err| Error::PathDataInvalidUtf8 {
37                    source: err,
38                    path: e.path.into(),
39                })?,
40                offset: e.offset,
41                length: e.length,
42            })
43        })
44    }
45
46    /// Return a list of the items in the archive.
47    pub fn list(&self) -> impl ExactSizeIterator<Item = crate::Utf8Entry<'_>> {
48        self.try_list().map(|r| {
49            r.expect("AsyncUtf8Reader::new only succeeds if try_list succeeds for every element")
50        })
51    }
52
53    /// Read the entire contents of an entry with the specified path.
54    /// O(log(# directory entries))
55    pub async fn read_file(&mut self, path: &str) -> Result<Vec<u8>, Error> {
56        self.reader.read_file(path.as_bytes()).await
57    }
58
59    pub fn into_source(self) -> T {
60        self.reader.into_source()
61    }
62}
63
64impl<T> AsyncUtf8Reader<Arc<Mutex<T>>>
65where
66    T: AsyncReadAt + AsyncGetSize + Unpin + Send,
67{
68    /// Read the contents of the entry with the specified path as a stream.
69    /// Each Vec in the stream will have a maximum size of `buffer_size`.
70    /// O(log(# directory entries))
71    pub fn read_file_stream(
72        &self,
73        path: &str,
74        buffer_size: usize,
75    ) -> Result<
76        (u64, impl futures::stream::Stream<Item = Result<Vec<u8>, std::io::Error>> + Send + use<T>),
77        Error,
78    > {
79        self.reader.read_file_stream(path.as_bytes(), buffer_size)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use assert_matches::assert_matches;
87    use fuchsia_fs::file::Adapter;
88    use futures::io::Cursor;
89
90    #[fuchsia::test]
91    async fn new_rejects_non_utf8_path() {
92        let mut far_bytes = vec![];
93        let () = crate::write::write(
94            &mut far_bytes,
95            std::collections::BTreeMap::from_iter([(
96                b"\xff",
97                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
98            )]),
99        )
100        .unwrap();
101
102        assert_matches!(
103            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await,
104            Err(crate::Error::PathDataInvalidUtf8{source: _, path}) if path == b"\xff".to_vec()
105        );
106    }
107
108    #[fuchsia::test]
109    async fn list_does_not_panic() {
110        let mut far_bytes = vec![];
111        let () = crate::write::write(
112            &mut far_bytes,
113            std::collections::BTreeMap::from_iter([(
114                "valid-utf8",
115                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
116            )]),
117        )
118        .unwrap();
119
120        itertools::assert_equal(
121            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await.unwrap().list(),
122            [crate::Utf8Entry { path: "valid-utf8", offset: 4096, length: 0 }],
123        );
124    }
125
126    #[fuchsia::test]
127    async fn read_file() {
128        let mut far_bytes = vec![];
129        let () = crate::write::write(
130            &mut far_bytes,
131            std::collections::BTreeMap::from_iter([(
132                "valid-utf8",
133                (12, Box::new("test-content".as_bytes()) as Box<dyn std::io::Read>),
134            )]),
135        )
136        .unwrap();
137
138        assert_eq!(
139            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes)))
140                .await
141                .unwrap()
142                .read_file("valid-utf8")
143                .await
144                .unwrap(),
145            b"test-content".to_vec()
146        );
147    }
148}