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),
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_async as fasync;
88    use fuchsia_fs::file::Adapter;
89    use futures::io::Cursor;
90
91    #[fasync::run_singlethreaded(test)]
92    async fn new_rejects_non_utf8_path() {
93        let mut far_bytes = vec![];
94        let () = crate::write::write(
95            &mut far_bytes,
96            std::collections::BTreeMap::from_iter([(
97                b"\xff",
98                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
99            )]),
100        )
101        .unwrap();
102
103        assert_matches!(
104            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await,
105            Err(crate::Error::PathDataInvalidUtf8{source: _, path}) if path == b"\xff".to_vec()
106        );
107    }
108
109    #[fasync::run_singlethreaded(test)]
110    async fn list_does_not_panic() {
111        let mut far_bytes = vec![];
112        let () = crate::write::write(
113            &mut far_bytes,
114            std::collections::BTreeMap::from_iter([(
115                "valid-utf8",
116                (0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
117            )]),
118        )
119        .unwrap();
120
121        itertools::assert_equal(
122            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await.unwrap().list(),
123            [crate::Utf8Entry { path: "valid-utf8", offset: 4096, length: 0 }],
124        );
125    }
126
127    #[fasync::run_singlethreaded(test)]
128    async fn read_file() {
129        let mut far_bytes = vec![];
130        let () = crate::write::write(
131            &mut far_bytes,
132            std::collections::BTreeMap::from_iter([(
133                "valid-utf8",
134                (12, Box::new("test-content".as_bytes()) as Box<dyn std::io::Read>),
135            )]),
136        )
137        .unwrap();
138
139        assert_eq!(
140            AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes)))
141                .await
142                .unwrap()
143                .read_file("valid-utf8")
144                .await
145                .unwrap(),
146            b"test-content".to_vec()
147        );
148    }
149}