1use crate::{DecodeContext, Error, Result};
19use std::fmt;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[non_exhaustive]
25pub enum DataUnit {
26 Payload,
28 Strip(u32),
30 Tile(u32),
32 Plane(u32),
34}
35
36impl DataUnit {
37 pub fn index(&self) -> Option<u32> {
39 match *self {
40 DataUnit::Payload => None,
41 DataUnit::Strip(i) | DataUnit::Tile(i) | DataUnit::Plane(i) => Some(i),
42 }
43 }
44
45 pub fn kind(&self) -> &'static str {
47 match self {
48 DataUnit::Payload => "payload",
49 DataUnit::Strip(_) => "strip",
50 DataUnit::Tile(_) => "tile",
51 DataUnit::Plane(_) => "plane",
52 }
53 }
54}
55
56impl fmt::Display for DataUnit {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self.index() {
59 Some(i) => write!(f, "{} {i}", self.kind()),
60 None => f.write_str("image data"),
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[non_exhaustive]
69pub struct Truncation {
70 pub unit: DataUnit,
72 pub offset: u64,
74 pub expected: u64,
76 pub available: u64,
78 pub file_len: u64,
80}
81
82impl Truncation {
83 pub fn new(unit: DataUnit, offset: u64, expected: u64, file_len: u64) -> Self {
85 let available = file_len.saturating_sub(offset).min(expected);
86 Truncation { unit, offset, expected, available, file_len }
87 }
88
89 pub fn missing(&self) -> u64 {
91 self.expected.saturating_sub(self.available)
92 }
93
94 pub fn starts_beyond_eof(&self) -> bool {
96 self.offset >= self.file_len
97 }
98
99 pub fn range_overflows(&self) -> bool {
101 self.offset.checked_add(self.expected).is_none_or(|e| usize::try_from(e).is_err())
102 }
103}
104
105impl fmt::Display for Truncation {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 let unit = match self.unit {
108 DataUnit::Payload => String::new(),
109 u => format!("{u} "),
110 };
111 if self.range_overflows() {
112 write!(
113 f,
114 "truncated image data: {unit}range of {} bytes at offset {} overflows (file is {} bytes)",
115 self.expected, self.offset, self.file_len
116 )
117 } else if self.starts_beyond_eof() {
118 write!(
119 f,
120 "truncated image data: {unit}expected {} bytes at offset {}, which is beyond the end of the file ({} bytes)",
121 self.expected, self.offset, self.file_len
122 )
123 } else {
124 write!(
125 f,
126 "truncated image data: {unit}expected {} bytes at offset {}, but only {} bytes remain before EOF",
127 self.expected, self.offset, self.available
128 )
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[non_exhaustive]
137pub enum CorruptReason {
138 Truncated(Truncation),
140 InvalidUnit {
142 unit: DataUnit,
144 message: String,
146 },
147 PreviewFallback {
151 message: String,
153 },
154}
155
156impl CorruptReason {
157 pub fn unit(&self) -> DataUnit {
159 match self {
160 CorruptReason::Truncated(t) => t.unit,
161 CorruptReason::InvalidUnit { unit, .. } => *unit,
162 CorruptReason::PreviewFallback { .. } => DataUnit::Payload,
163 }
164 }
165
166 pub fn kind(&self) -> &'static str {
168 match self {
169 CorruptReason::Truncated(_) => "truncated",
170 CorruptReason::InvalidUnit { .. } => "invalid-unit",
171 CorruptReason::PreviewFallback { .. } => "preview-fallback",
172 }
173 }
174
175 pub fn truncation(&self) -> Option<&Truncation> {
177 match self {
178 CorruptReason::Truncated(t) => Some(t),
179 _ => None,
180 }
181 }
182}
183
184impl fmt::Display for CorruptReason {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 CorruptReason::Truncated(t) => t.fmt(f),
188 CorruptReason::InvalidUnit { unit, message } => write!(f, "corrupt {unit}: {message}"),
189 CorruptReason::PreviewFallback { message } => write!(f, "sensor data not decoded: {message}"),
190 }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199#[non_exhaustive]
200pub struct DecodeWarning {
201 pub reason: CorruptReason,
203 pub samples_filled: u64,
205}
206
207impl DecodeWarning {
208 pub fn new(reason: CorruptReason, samples_filled: u64) -> Self {
210 DecodeWarning { reason, samples_filled }
211 }
212
213 pub fn kind(&self) -> &'static str {
215 self.reason.kind()
216 }
217}
218
219impl fmt::Display for DecodeWarning {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match &self.reason {
222 CorruptReason::Truncated(t) => write!(
223 f,
224 "recovered truncated {}: {} / {} bytes available; {} missing samples were zero-filled",
225 t.unit, t.available, t.expected, self.samples_filled
226 ),
227 CorruptReason::InvalidUnit { unit, message } => {
228 write!(f, "recovered corrupt {unit} ({message}); its {} samples were zero-filled", self.samples_filled)
229 }
230 CorruptReason::PreviewFallback { message } => {
231 write!(f, "sensor data not decoded ({message}); the frame holds the embedded full-size preview")
232 }
233 }
234 }
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub struct RowGeometry {
241 pub rows: usize,
243 pub stride: usize,
245 pub row_bytes: usize,
247 pub samples: usize,
249}
250
251impl RowGeometry {
252 pub fn packed(rows: usize, samples: usize, bits: u32, stride: usize) -> Self {
254 let row_bytes = (samples.saturating_mul(bits as usize)).div_ceil(8);
255 RowGeometry { rows, stride: stride.max(row_bytes), row_bytes, samples }
256 }
257
258 pub fn tight(rows: usize, samples: usize, bits: u32) -> Self {
260 let row_bytes = (samples.saturating_mul(bits as usize)).div_ceil(8);
261 RowGeometry { rows, stride: row_bytes, row_bytes, samples }
262 }
263
264 pub fn needed(&self) -> usize {
266 if self.rows == 0 {
267 return 0;
268 }
269 (self.rows - 1).saturating_mul(self.stride).saturating_add(self.row_bytes)
270 }
271
272 pub fn total(&self) -> u64 {
274 self.rows as u64 * self.samples as u64
275 }
276
277 pub fn complete_rows(&self, avail: usize) -> usize {
279 if avail < self.row_bytes || self.stride == 0 {
280 return 0;
281 }
282 ((avail - self.row_bytes) / self.stride + 1).min(self.rows)
283 }
284
285 pub fn complete_samples(&self, avail: usize) -> u64 {
288 let rows = self.complete_rows(avail);
289 if rows >= self.rows {
290 return self.total();
291 }
292 let rest = avail.saturating_sub(rows * self.stride).min(self.row_bytes);
293 let partial = if self.row_bytes == 0 { 0 } else { rest as u64 * self.samples as u64 / self.row_bytes as u64 };
294 rows as u64 * self.samples as u64 + partial
295 }
296
297 pub fn missing_samples(&self, avail: usize) -> u64 {
299 self.total() - self.complete_samples(avail)
300 }
301}
302
303pub fn check_range(file_len: usize, unit: DataUnit, offset: usize, needed: usize) -> Result<(), Truncation> {
306 if needed == 0 {
307 return Ok(());
308 }
309 match offset.checked_add(needed) {
310 Some(end) if end <= file_len => Ok(()),
311 _ => Err(Truncation::new(unit, offset as u64, needed as u64, file_len as u64)),
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct SensorBytes<'a> {
318 pub bytes: &'a [u8],
320 pub truncation: Option<Truncation>,
323}
324
325impl<'a> SensorBytes<'a> {
326 pub fn is_complete(&self) -> bool {
328 self.truncation.is_none()
329 }
330}
331
332impl DecodeContext<'_> {
333 pub fn sensor_bytes<'a>(
339 &self,
340 data: &'a [u8],
341 unit: DataUnit,
342 offset: usize,
343 needed: usize,
344 ) -> Result<SensorBytes<'a>> {
345 match check_range(data.len(), unit, offset, needed) {
346 Ok(()) => Ok(SensorBytes { bytes: data.get(offset..offset + needed).unwrap_or(&[]), truncation: None }),
347 Err(t) if self.recover => {
348 let start = offset.min(data.len());
349 let end = start.saturating_add(needed).min(data.len());
350 Ok(SensorBytes { bytes: &data[start..end], truncation: Some(t) })
351 }
352 Err(t) => Err(Error::truncated(t)),
353 }
354 }
355
356 pub fn sensor_rows<'a>(
360 &self,
361 data: &'a [u8],
362 unit: DataUnit,
363 offset: usize,
364 geometry: RowGeometry,
365 ) -> Result<&'a [u8]> {
366 let s = self.sensor_bytes(data, unit, offset, geometry.needed())?;
367 if let Some(t) = s.truncation {
368 let filled = geometry.missing_samples(s.bytes.len());
369 self.recovered(CorruptReason::Truncated(t), filled);
370 }
371 Ok(s.bytes)
372 }
373
374 pub fn truncated(&self, t: Truncation, samples_filled: u64) -> Result<()> {
377 if self.recover {
378 self.recovered(CorruptReason::Truncated(t), samples_filled);
379 Ok(())
380 } else {
381 Err(Error::truncated(t))
382 }
383 }
384
385 pub fn recovered(&self, reason: CorruptReason, samples_filled: u64) {
387 self.warnings.lock().unwrap_or_else(|p| p.into_inner()).push(DecodeWarning::new(reason, samples_filled));
388 }
389
390 pub fn take_warnings(&self) -> Vec<DecodeWarning> {
392 let mut w = std::mem::take(&mut *self.warnings.lock().unwrap_or_else(|p| p.into_inner()));
393 w.sort_by_key(|w| {
394 let u = w.reason.unit();
395 (u.kind(), u.index())
396 });
397 w
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use crate::Profiler;
405
406 #[test]
407 fn messages() {
408 let t = Truncation::new(DataUnit::Strip(17), 24_576_000, 131_072, 24_576_000 + 48_291);
409 assert_eq!(t.available, 48_291);
410 assert_eq!(t.missing(), 131_072 - 48_291);
411 assert_eq!(
412 t.to_string(),
413 "truncated image data: strip 17 expected 131072 bytes at offset 24576000, but only 48291 bytes remain before EOF"
414 );
415 let e = Error::truncated(t.clone());
416 assert_eq!(e.kind(), "CorruptFile");
417 assert_eq!(e.truncation(), Some(&t));
418 assert!(e.to_string().starts_with("corrupt file: truncated image data: strip 17"));
419 let w = DecodeWarning::new(CorruptReason::Truncated(t), 10);
420 assert_eq!(
421 w.to_string(),
422 "recovered truncated strip 17: 48291 / 131072 bytes available; 10 missing samples were zero-filled"
423 );
424 let beyond = Truncation::new(DataUnit::Tile(2), 1000, 10, 500);
425 assert!(beyond.starts_beyond_eof());
426 assert_eq!(beyond.available, 0);
427 assert!(beyond.to_string().contains("beyond the end of the file"));
428 assert!(Truncation::new(DataUnit::Payload, u64::MAX - 1, 10, 5).range_overflows());
429 }
430
431 #[test]
432 fn geometry() {
433 let g = RowGeometry::packed(3, 4, 12, 8);
435 assert_eq!(g.needed(), 22);
436 assert_eq!(g.complete_rows(22), 3);
437 assert_eq!(g.complete_rows(21), 2);
438 assert_eq!(g.complete_samples(21), 8 + 3);
439 assert_eq!(g.missing_samples(0), 12);
440 assert_eq!(g.missing_samples(100), 0);
441 }
442
443 #[test]
444 fn policies() {
445 let p = Profiler::new();
446 let data = [0u8; 10];
447 let mut ctx = DecodeContext::new(&p);
448 assert!(ctx.recover, "recover is the default");
449 ctx.recover = false;
450 assert_eq!(ctx.sensor_bytes(&data, DataUnit::Payload, 2, 8).unwrap().bytes.len(), 8);
451 let e = ctx.sensor_bytes(&data, DataUnit::Strip(1), 4, 8).unwrap_err();
452 assert_eq!(e.truncation().unwrap().available, 6);
453 assert!(ctx.sensor_bytes(&data, DataUnit::Strip(1), usize::MAX, 8).is_err());
454 assert!(ctx.sensor_bytes(&data, DataUnit::Strip(1), 100, 0).unwrap().bytes.is_empty());
455 ctx.recover = true;
456 let s = ctx.sensor_bytes(&data, DataUnit::Strip(1), 4, 8).unwrap();
457 assert_eq!(s.bytes.len(), 6);
458 assert!(!s.is_complete());
459 let s = ctx.sensor_bytes(&data, DataUnit::Strip(2), usize::MAX, 8).unwrap();
460 assert!(s.bytes.is_empty());
461 assert_eq!(ctx.sensor_rows(&data, DataUnit::Tile(0), 4, RowGeometry::tight(2, 4, 8)).unwrap().len(), 6);
462 let w = ctx.take_warnings();
463 assert_eq!(w.len(), 1);
464 assert_eq!(w[0].samples_filled, 2);
465 }
466}