blob: 2d1b617dbcecbdd8f6b31d4999c3f29224364803 [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;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080022
23use crate::common::COMMON_PAGE_SIZE;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080024
25/// A trait for reading data by chunks. The data is assumed readonly and has fixed length. Chunks
26/// can be read by specifying the chunk index. Only the last chunk may have incomplete chunk size.
27pub trait ReadOnlyDataByChunk {
28 /// Default chunk size.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080029 const CHUNK_SIZE: u64 = COMMON_PAGE_SIZE;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080030
31 /// Read the `chunk_index`-th chunk to `buf`. Each slice/chunk has size `CHUNK_SIZE` except for
32 /// the last one, which can be an incomplete chunk. `buf` is currently required to be large
33 /// enough to hold a full chunk of data. Reading beyond the file size (including empty file)
34 /// will crash.
35 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize>;
36}
37
38fn chunk_index_to_range(size: u64, chunk_size: u64, chunk_index: u64) -> Result<(u64, u64)> {
39 let start = chunk_index * chunk_size;
40 assert!(start < size);
41 let end = std::cmp::min(size, start + chunk_size);
42 Ok((start, end))
43}
44
45/// A read-only file that can be read by chunks.
46pub struct ChunkedFileReader {
47 file: File,
48 size: u64,
49}
50
51impl ChunkedFileReader {
52 /// Creates a `ChunkedFileReader` to read from for the specified `path`.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080053 pub fn new(file: File) -> Result<ChunkedFileReader> {
Victor Hsieh1fe51c42020-11-05 11:08:10 -080054 let size = file.metadata()?.len();
55 Ok(ChunkedFileReader { file, size })
56 }
57}
58
59impl ReadOnlyDataByChunk for ChunkedFileReader {
60 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
61 debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
62 let (start, end) = chunk_index_to_range(self.size, Self::CHUNK_SIZE, chunk_index)?;
63 let size = (end - start) as usize;
64 self.file.read_at(&mut buf[..size], start)
65 }
66}
67
68impl ReadOnlyDataByChunk for &[u8] {
69 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
70 debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
71 let chunk = &self.chunks(Self::CHUNK_SIZE as usize).nth(chunk_index as usize).unwrap();
72 buf[..chunk.len()].copy_from_slice(&chunk);
73 Ok(chunk.len())
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 fn test_reading_more_than_4kb_data<T: ReadOnlyDataByChunk>(
82 reader: T,
83 data_size: u64,
84 ) -> Result<()> {
85 let mut buf = [0u8; 4096];
86 assert_eq!(reader.read_chunk(0, &mut buf)?, 4096);
87 let last_index = (data_size + 4095) / 4096 - 1;
88 assert_eq!(reader.read_chunk(last_index, &mut buf)?, (data_size % 4096) as usize);
89 Ok(())
90 }
91
92 // TODO(victorhsieh): test ChunkedFileReader once there is a way to access testdata in the test
93 // environement.
94
95 #[test]
96 fn test_read_in_memory_data() -> Result<()> {
97 let data = &[1u8; 5000][..];
98 test_reading_more_than_4kb_data(data, data.len() as u64)
99 }
100
101 #[test]
102 #[should_panic]
103 #[allow(unused_must_use)]
104 fn test_read_in_memory_empty_data() {
105 let data = &[][..]; // zero length slice
106 let mut buf = [0u8; 4096];
107 data.read_chunk(0, &mut buf); // should panic
108 }
109
110 #[test]
111 #[should_panic]
112 #[allow(unused_must_use)]
113 fn test_read_beyond_file_size() {
114 let data = &[1u8; 5000][..];
115 let mut buf = [0u8; 4096];
116 let last_index_plus_1 = (data.len() + 4095) / 4096;
117 data.read_chunk(last_index_plus_1 as u64, &mut buf); // should panic
118 }
119}