1use std::io::{self, Read};
2
3#[cfg(feature = "fs")]
4use std::path::{Path, PathBuf};
5
6use super::{DecodeError, DecodeLimits, decode};
7use crate::limits::{LimitError, Limits, ResourceKind};
8use crate::{Pack, PackArchiveBytes};
9
10pub type ReadResource = ResourceKind<7>;
12
13#[allow(non_upper_case_globals)]
14impl ResourceKind<7> {
15 pub const ArchiveBytes: Self = Self::new(0);
16}
17
18pub type ReadLimitError = LimitError<ReadResource>;
20
21pub type ReadLimits = Limits<ReadResource>;
23
24impl Limits<ReadResource> {
25 #[track_caller]
27 pub fn new(archive_bytes: u64) -> Self {
28 Self::from_ceilings([archive_bytes, 0, 0, 0, 0, 0, 0])
29 .assert_probe_resources([ReadResource::ArchiveBytes])
30 }
31
32 pub const fn reference_v1() -> Self {
34 Self::from_ceilings([512 * 1024 * 1024, 0, 0, 0, 0, 0, 0])
35 }
36
37 pub const fn archive_bytes(&self) -> u64 {
38 self.ceilings[0]
39 }
40}
41
42#[derive(Debug, thiserror::Error)]
44#[non_exhaustive]
45pub enum ReadError {
46 #[error(transparent)]
47 Limit(#[from] ReadLimitError),
48 #[error("failed to read Pack Archive bytes: {0}")]
49 Read(#[source] io::Error),
50}
51
52pub fn read(mut reader: impl Read, limits: ReadLimits) -> Result<PackArchiveBytes, ReadError> {
54 const BUFFER_BYTES: usize = 8 * 1024;
55
56 let mut bytes = Vec::new();
57 let mut buffer = [0; BUFFER_BYTES];
58 let mut observed = 0u64;
59 loop {
60 let probe_end =
61 limits
62 .archive_bytes()
63 .checked_add(1)
64 .ok_or(ReadLimitError::AccountingOverflow {
65 resource: ReadResource::ArchiveBytes,
66 })?;
67 let remaining =
68 probe_end
69 .checked_sub(observed)
70 .ok_or(ReadLimitError::AccountingOverflow {
71 resource: ReadResource::ArchiveBytes,
72 })?;
73 let read_length = usize::try_from(remaining.min(BUFFER_BYTES as u64)).map_err(|_| {
74 ReadLimitError::AccountingOverflow {
75 resource: ReadResource::ArchiveBytes,
76 }
77 })?;
78 let read = match reader.read(&mut buffer[..read_length]) {
79 Ok(read) => read,
80 Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
81 Err(error) => return Err(ReadError::Read(error)),
82 };
83 if read == 0 {
84 return Ok(PackArchiveBytes::from_vec(bytes));
85 }
86 observed = observed
87 .checked_add(
88 u64::try_from(read).map_err(|_| ReadLimitError::AccountingOverflow {
89 resource: ReadResource::ArchiveBytes,
90 })?,
91 )
92 .ok_or(ReadLimitError::AccountingOverflow {
93 resource: ReadResource::ArchiveBytes,
94 })?;
95 if observed > limits.archive_bytes() {
96 return Err(ReadLimitError::exceeded(
97 ReadResource::ArchiveBytes,
98 limits.archive_bytes(),
99 )
100 .into());
101 }
102 bytes.extend_from_slice(&buffer[..read]);
103 }
104}
105
106#[derive(Debug, thiserror::Error)]
108#[non_exhaustive]
109pub enum ReadPackError {
110 #[error(transparent)]
111 Read(#[from] ReadError),
112 #[error("read bytes could not be decoded as a Pack: {source}")]
113 Decode {
114 archive: PackArchiveBytes,
115 #[source]
116 source: DecodeError,
117 },
118}
119
120pub fn read_pack(
122 reader: impl Read,
123 read_limits: ReadLimits,
124 decode_limits: DecodeLimits,
125) -> Result<Pack, ReadPackError> {
126 let archive = read(reader, read_limits)?;
127 match decode(&archive, decode_limits) {
128 Ok(pack) => Ok(pack),
129 Err(source) => Err(ReadPackError::Decode { archive, source }),
130 }
131}
132
133#[cfg(feature = "fs")]
135#[derive(Debug, Clone, Copy, Eq, PartialEq)]
136#[non_exhaustive]
137pub enum FileReadPhase {
138 Open,
139 Metadata,
140 Read,
141}
142
143#[cfg(feature = "fs")]
145#[derive(Debug, thiserror::Error)]
146#[non_exhaustive]
147pub enum FileReadError {
148 #[error("failed to open Pack Archive {path:?}: {source}")]
149 Open {
150 path: PathBuf,
151 #[source]
152 source: io::Error,
153 },
154 #[error("failed to inspect Pack Archive {path:?}: {source}")]
155 Metadata {
156 path: PathBuf,
157 #[source]
158 source: io::Error,
159 },
160 #[error("Pack Archive {path:?} exceeds its read limit: {source}")]
161 Limit {
162 path: PathBuf,
163 phase: FileReadPhase,
164 #[source]
165 source: ReadLimitError,
166 },
167 #[error("failed to read Pack Archive {path:?}: {source}")]
168 Read {
169 path: PathBuf,
170 #[source]
171 source: io::Error,
172 },
173}
174
175#[cfg(feature = "fs")]
176impl FileReadError {
177 pub const fn phase(&self) -> FileReadPhase {
178 match self {
179 Self::Open { .. } => FileReadPhase::Open,
180 Self::Metadata { .. } => FileReadPhase::Metadata,
181 Self::Limit { phase, .. } => *phase,
182 Self::Read { .. } => FileReadPhase::Read,
183 }
184 }
185
186 pub fn path(&self) -> &Path {
187 match self {
188 Self::Open { path, .. }
189 | Self::Metadata { path, .. }
190 | Self::Limit { path, .. }
191 | Self::Read { path, .. } => path,
192 }
193 }
194}
195
196#[cfg(feature = "fs")]
198pub fn read_file(
199 path: impl AsRef<Path>,
200 limits: ReadLimits,
201) -> Result<PackArchiveBytes, FileReadError> {
202 let path = path.as_ref();
203 let file = std::fs::File::open(path).map_err(|source| FileReadError::Open {
204 path: path.to_owned(),
205 source,
206 })?;
207 let known_size = file
208 .metadata()
209 .map_err(|source| FileReadError::Metadata {
210 path: path.to_owned(),
211 source,
212 })?
213 .len();
214 if known_size > limits.archive_bytes() {
215 return Err(FileReadError::Limit {
216 path: path.to_owned(),
217 phase: FileReadPhase::Metadata,
218 source: ReadLimitError::exceeded(ReadResource::ArchiveBytes, limits.archive_bytes()),
219 });
220 }
221 read(file, limits).map_err(|error| match error {
222 ReadError::Limit(source) => FileReadError::Limit {
223 path: path.to_owned(),
224 phase: FileReadPhase::Read,
225 source,
226 },
227 ReadError::Read(source) => FileReadError::Read {
228 path: path.to_owned(),
229 source,
230 },
231 })
232}
233
234#[cfg(feature = "fs")]
236#[derive(Debug, thiserror::Error)]
237#[non_exhaustive]
238pub enum OpenPackError {
239 #[error(transparent)]
240 Read(#[from] FileReadError),
241 #[error("read bytes from {path:?} could not be decoded as a Pack: {source}")]
242 Decode {
243 path: PathBuf,
244 archive: PackArchiveBytes,
245 #[source]
246 source: Box<DecodeError>,
247 },
248}
249
250#[cfg(feature = "fs")]
252pub fn open_pack(
253 path: impl AsRef<Path>,
254 read_limits: ReadLimits,
255 decode_limits: DecodeLimits,
256) -> Result<Pack, OpenPackError> {
257 let path = path.as_ref();
258 let archive = read_file(path, read_limits)?;
259 match decode(&archive, decode_limits) {
260 Ok(pack) => Ok(pack),
261 Err(source) => Err(OpenPackError::Decode {
262 path: path.to_owned(),
263 archive,
264 source: Box::new(source),
265 }),
266 }
267}