blob: 449fdcc31f016e8638a50e36ee4fefae466cef7a [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::error::Error,
fuchsia_fs::file::{AsyncGetSize, AsyncReadAt},
};
/// A struct to open and read a FAR-formatted archive asynchronously.
/// Requires that all paths are valid UTF-8.
#[derive(Debug)]
pub struct AsyncUtf8Reader<T>
where
T: AsyncReadAt + AsyncGetSize + Unpin,
{
reader: crate::async_read::AsyncReader<T>,
}
impl<T> AsyncUtf8Reader<T>
where
T: AsyncReadAt + AsyncGetSize + Unpin,
{
/// Create a new AsyncUtf8Reader for the provided source.
pub async fn new(source: T) -> Result<Self, Error> {
let ret = Self { reader: crate::async_read::AsyncReader::new(source).await? };
let () = ret.try_list().try_for_each(|r| r.map(|_| ()))?;
Ok(ret)
}
/// Return a list of the items in the archive.
/// Individual items will error if their paths are not valid UTF-8.
fn try_list(&self) -> impl ExactSizeIterator<Item = Result<crate::Utf8Entry<'_>, Error>> {
self.reader.list().map(|e| {
Ok(crate::Utf8Entry {
path: std::str::from_utf8(e.path).map_err(|err| Error::PathDataInvalidUtf8 {
source: err,
path: e.path.into(),
})?,
offset: e.offset,
length: e.length,
})
})
}
/// Return a list of the items in the archive.
pub fn list(&self) -> impl ExactSizeIterator<Item = crate::Utf8Entry<'_>> {
self.try_list().map(|r| {
r.expect("AsyncUtf8Reader::new only succeeds if try_list succeeds for every element")
})
}
/// Read the entire contents of an entry with the specified path.
/// O(log(# directory entries))
pub async fn read_file(&mut self, path: &str) -> Result<Vec<u8>, Error> {
self.reader.read_file(path.as_bytes()).await
}
}
#[cfg(test)]
mod tests {
use {
super::*, assert_matches::assert_matches, fuchsia_async as fasync,
fuchsia_fs::file::Adapter, futures::io::Cursor,
};
#[fasync::run_singlethreaded(test)]
async fn new_rejects_non_utf8_path() {
let mut far_bytes = vec![];
let () = crate::write::write(
&mut far_bytes,
std::collections::BTreeMap::from_iter([(
b"\xff",
(0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
)]),
)
.unwrap();
assert_matches!(
AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await,
Err(crate::Error::PathDataInvalidUtf8{source: _, path}) if path == b"\xff".to_vec()
);
}
#[fasync::run_singlethreaded(test)]
async fn list_does_not_panic() {
let mut far_bytes = vec![];
let () = crate::write::write(
&mut far_bytes,
std::collections::BTreeMap::from_iter([(
"valid-utf8",
(0, Box::new("".as_bytes()) as Box<dyn std::io::Read>),
)]),
)
.unwrap();
itertools::assert_equal(
AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes))).await.unwrap().list(),
[crate::Utf8Entry { path: "valid-utf8", offset: 4096, length: 0 }],
);
}
#[fasync::run_singlethreaded(test)]
async fn read_file() {
let mut far_bytes = vec![];
let () = crate::write::write(
&mut far_bytes,
std::collections::BTreeMap::from_iter([(
"valid-utf8",
(12, Box::new("test-content".as_bytes()) as Box<dyn std::io::Read>),
)]),
)
.unwrap();
assert_eq!(
AsyncUtf8Reader::new(Adapter::new(Cursor::new(far_bytes)))
.await
.unwrap()
.read_file("valid-utf8")
.await
.unwrap(),
b"test-content".to_vec()
);
}
}