blob: 135a79381f4e4cd822653440a9c3ccdc6c9f13e0 [file] [log] [blame]
Victor Hsieh1fe51c42020-11-05 11:08:10 -08001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! A module for reading data by chunks.
18
19use std::fs::File;
20use std::io::Result;
21use std::os::unix::fs::FileExt;
22use std::path::Path;
23
24/// A trait for reading data by chunks. The data is assumed readonly and has fixed length. Chunks
25/// can be read by specifying the chunk index. Only the last chunk may have incomplete chunk size.
26pub trait ReadOnlyDataByChunk {
27 /// Default chunk size.
28 const CHUNK_SIZE: u64 = 4096;
29
30 /// Read the `chunk_index`-th chunk to `buf`. Each slice/chunk has size `CHUNK_SIZE` except for
31 /// the last one, which can be an incomplete chunk. `buf` is currently required to be large
32 /// enough to hold a full chunk of data. Reading beyond the file size (including empty file)
33 /// will crash.
34 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize>;
35}
36
37fn chunk_index_to_range(size: u64, chunk_size: u64, chunk_index: u64) -> Result<(u64, u64)> {
38 let start = chunk_index * chunk_size;
39 assert!(start < size);
40 let end = std::cmp::min(size, start + chunk_size);
41 Ok((start, end))
42}
43
44/// A read-only file that can be read by chunks.
45pub struct ChunkedFileReader {
46 file: File,
47 size: u64,
48}
49
50impl ChunkedFileReader {
51 /// Creates a `ChunkedFileReader` to read from for the specified `path`.
52 #[allow(dead_code)]
53 pub fn new<P: AsRef<Path>>(path: P) -> Result<ChunkedFileReader> {
54 let file = File::open(path)?;
55 let size = file.metadata()?.len();
56 Ok(ChunkedFileReader { file, size })
57 }
58}
59
60impl ReadOnlyDataByChunk for ChunkedFileReader {
61 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
62 debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
63 let (start, end) = chunk_index_to_range(self.size, Self::CHUNK_SIZE, chunk_index)?;
64 let size = (end - start) as usize;
65 self.file.read_at(&mut buf[..size], start)
66 }
67}
68
69impl ReadOnlyDataByChunk for &[u8] {
70 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
71 debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
72 let chunk = &self.chunks(Self::CHUNK_SIZE as usize).nth(chunk_index as usize).unwrap();
73 buf[..chunk.len()].copy_from_slice(&chunk);
74 Ok(chunk.len())
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 fn test_reading_more_than_4kb_data<T: ReadOnlyDataByChunk>(
83 reader: T,
84 data_size: u64,
85 ) -> Result<()> {
86 let mut buf = [0u8; 4096];
87 assert_eq!(reader.read_chunk(0, &mut buf)?, 4096);
88 let last_index = (data_size + 4095) / 4096 - 1;
89 assert_eq!(reader.read_chunk(last_index, &mut buf)?, (data_size % 4096) as usize);
90 Ok(())
91 }
92
93 // TODO(victorhsieh): test ChunkedFileReader once there is a way to access testdata in the test
94 // environement.
95
96 #[test]
97 fn test_read_in_memory_data() -> Result<()> {
98 let data = &[1u8; 5000][..];
99 test_reading_more_than_4kb_data(data, data.len() as u64)
100 }
101
102 #[test]
103 #[should_panic]
104 #[allow(unused_must_use)]
105 fn test_read_in_memory_empty_data() {
106 let data = &[][..]; // zero length slice
107 let mut buf = [0u8; 4096];
108 data.read_chunk(0, &mut buf); // should panic
109 }
110
111 #[test]
112 #[should_panic]
113 #[allow(unused_must_use)]
114 fn test_read_beyond_file_size() {
115 let data = &[1u8; 5000][..];
116 let mut buf = [0u8; 4096];
117 let last_index_plus_1 = (data.len() + 4095) / 4096;
118 data.read_chunk(last_index_plus_1 as u64, &mut buf); // should panic
119 }
120}