Skip to main content

object/read/
mod.rs

1//! Interface for reading object files.
2//!
3//! ## Unified read API
4//!
5//! The [`Object`] trait provides a unified read API for accessing common features of
6//! object files, such as sections and symbols. There is an implementation of this
7//! trait for [`File`], which allows reading any file format, as well as implementations
8//! for each file format:
9//! [`ElfFile`](elf::ElfFile), [`MachOFile`](macho::MachOFile), [`CoffFile`](coff::CoffFile),
10//! [`PeFile`](pe::PeFile), [`WasmFile`](wasm::WasmFile), [`XcoffFile`](xcoff::XcoffFile).
11//!
12//! ## Low level read API
13//!
14//! The submodules for each file format define helpers that operate on the raw structs.
15//! These can be used instead of the unified API, or in conjunction with it to access
16//! details that are not available via the unified API.
17//!
18//! See the [submodules](#modules) for examples of the low level read API.
19//!
20//! ## Naming Convention
21//!
22//! Types that form part of the unified API for a file format are prefixed with the
23//! name of the file format.
24//!
25//! ## Example for unified read API
26//!  ```no_run
27//! use object::{Object, ObjectSection};
28//! use std::error::Error;
29//! use std::fs;
30//!
31//! /// Reads a file and displays the name of each section.
32//! fn main() -> Result<(), Box<dyn Error>> {
33//! #   #[cfg(all(feature = "read", feature = "std"))] {
34//!     let data = fs::read("path/to/binary")?;
35//!     let file = object::File::parse(&*data)?;
36//!     for section in file.sections() {
37//!         println!("{}", section.name()?);
38//!     }
39//! #   }
40//!     Ok(())
41//! }
42//! ```
43
44use alloc::borrow::Cow;
45use core::{fmt, result};
46
47#[cfg(not(feature = "std"))]
48use alloc::collections::btree_map::BTreeMap as Map;
49#[cfg(feature = "std")]
50use std::collections::hash_map::HashMap as Map;
51
52pub use crate::common::*;
53
54mod read_ref;
55pub use read_ref::*;
56
57mod read_cache;
58pub use read_cache::*;
59
60mod symbol_map;
61pub use symbol_map::*;
62
63mod util;
64pub use util::*;
65
66#[cfg(any(feature = "elf", feature = "macho"))]
67mod gnu_compression;
68
69#[cfg(any(
70    feature = "coff",
71    feature = "elf",
72    feature = "macho",
73    feature = "pe",
74    feature = "wasm",
75    feature = "xcoff"
76))]
77mod any;
78#[cfg(any(
79    feature = "coff",
80    feature = "elf",
81    feature = "macho",
82    feature = "pe",
83    feature = "wasm",
84    feature = "xcoff"
85))]
86pub use any::*;
87
88#[cfg(feature = "archive")]
89pub mod archive;
90
91#[cfg(feature = "coff")]
92pub mod coff;
93
94#[cfg(feature = "elf")]
95pub mod elf;
96
97#[cfg(feature = "macho")]
98pub mod macho;
99
100#[cfg(feature = "pe")]
101pub mod pe;
102
103#[cfg(feature = "wasm")]
104pub mod wasm;
105
106#[cfg(feature = "xcoff")]
107pub mod xcoff;
108
109mod traits;
110pub use traits::*;
111
112mod private {
113    pub trait Sealed {}
114}
115
116/// The error type used within the read module.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct Error(pub(crate) &'static str);
119
120impl fmt::Display for Error {
121    #[inline]
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.write_str(self.0)
124    }
125}
126
127#[cfg(feature = "std")]
128impl std::error::Error for Error {}
129#[cfg(all(not(feature = "std"), core_error))]
130impl core::error::Error for Error {}
131
132/// The result type used within the read module.
133pub type Result<T> = result::Result<T, Error>;
134
135trait ReadError<T> {
136    fn read_error(self, error: &'static str) -> Result<T>;
137}
138
139impl<T> ReadError<T> for result::Result<T, ()> {
140    fn read_error(self, error: &'static str) -> Result<T> {
141        self.map_err(|()| Error(error))
142    }
143}
144
145impl<T> ReadError<T> for result::Result<T, Error> {
146    fn read_error(self, error: &'static str) -> Result<T> {
147        self.map_err(|_| Error(error))
148    }
149}
150
151impl<T> ReadError<T> for Option<T> {
152    fn read_error(self, error: &'static str) -> Result<T> {
153        self.ok_or(Error(error))
154    }
155}
156
157/// The native executable file for the target platform.
158#[cfg(all(
159    unix,
160    not(target_vendor = "apple"),
161    not(target_os = "aix"),
162    feature = "elf"
163))]
164pub type NativeFile<'data, R = &'data [u8]> = elf::NativeElfFile<'data, R>;
165
166/// The native executable file for the target platform.
167#[cfg(all(target_vendor = "apple", feature = "macho"))]
168pub type NativeFile<'data, R = &'data [u8]> = macho::NativeMachOFile<'data, R>;
169
170/// The native executable file for the target platform.
171#[cfg(all(target_os = "windows", feature = "pe"))]
172pub type NativeFile<'data, R = &'data [u8]> = pe::NativePeFile<'data, R>;
173
174/// The native executable file for the target platform.
175#[cfg(all(target_family = "wasm", feature = "wasm"))]
176pub type NativeFile<'data, R = &'data [u8]> = wasm::WasmFile<'data, R>;
177
178/// The native executable file for the target platform.
179#[cfg(all(target_os = "aix", feature = "xcoff"))]
180pub type NativeFile<'data, R = &'data [u8]> = xcoff::NativeXcoffFile<'data, R>;
181
182/// A file format kind.
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
184#[non_exhaustive]
185pub enum FileKind {
186    /// A Unix archive.
187    ///
188    /// See [`archive::ArchiveFile`].
189    #[cfg(feature = "archive")]
190    Archive,
191    /// A COFF object file.
192    ///
193    /// See [`coff::CoffFile`].
194    #[cfg(feature = "coff")]
195    Coff,
196    /// A COFF bigobj object file.
197    ///
198    /// This supports a larger number of sections.
199    ///
200    /// See [`coff::CoffBigFile`].
201    #[cfg(feature = "coff")]
202    CoffBig,
203    /// A Windows short import file.
204    ///
205    /// See [`coff::ImportFile`].
206    #[cfg(feature = "coff")]
207    CoffImport,
208    /// A dyld cache file containing Mach-O images.
209    ///
210    /// See [`macho::DyldCache`]
211    #[cfg(feature = "macho")]
212    DyldCache,
213    /// A 32-bit ELF file.
214    ///
215    /// See [`elf::ElfFile32`].
216    #[cfg(feature = "elf")]
217    Elf32,
218    /// A 64-bit ELF file.
219    ///
220    /// See [`elf::ElfFile64`].
221    #[cfg(feature = "elf")]
222    Elf64,
223    /// A 32-bit Mach-O file.
224    ///
225    /// See [`macho::MachOFile32`].
226    #[cfg(feature = "macho")]
227    MachO32,
228    /// A 64-bit Mach-O file.
229    ///
230    /// See [`macho::MachOFile64`].
231    #[cfg(feature = "macho")]
232    MachO64,
233    /// A 32-bit Mach-O fat binary.
234    ///
235    /// See [`macho::MachOFatFile32`].
236    #[cfg(feature = "macho")]
237    MachOFat32,
238    /// A 64-bit Mach-O fat binary.
239    ///
240    /// See [`macho::MachOFatFile64`].
241    #[cfg(feature = "macho")]
242    MachOFat64,
243    /// A 32-bit PE file.
244    ///
245    /// See [`pe::PeFile32`].
246    #[cfg(feature = "pe")]
247    Pe32,
248    /// A 64-bit PE file.
249    ///
250    /// See [`pe::PeFile64`].
251    #[cfg(feature = "pe")]
252    Pe64,
253    /// A Wasm file.
254    ///
255    /// See [`wasm::WasmFile`].
256    #[cfg(feature = "wasm")]
257    Wasm,
258    /// A 32-bit XCOFF file.
259    ///
260    /// See [`xcoff::XcoffFile32`].
261    #[cfg(feature = "xcoff")]
262    Xcoff32,
263    /// A 64-bit XCOFF file.
264    ///
265    /// See [`xcoff::XcoffFile64`].
266    #[cfg(feature = "xcoff")]
267    Xcoff64,
268}
269
270impl FileKind {
271    /// Determine a file kind by parsing the start of the file.
272    pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<FileKind> {
273        Self::parse_at(data, 0)
274    }
275
276    /// Determine a file kind by parsing at the given offset.
277    pub fn parse_at<'data, R: ReadRef<'data>>(data: R, offset: u64) -> Result<FileKind> {
278        let magic = data
279            .read_bytes_at(offset, 16)
280            .read_error("Could not read file magic")?;
281        if magic.len() < 16 {
282            return Err(Error("File too short"));
283        }
284
285        let kind = match [magic[0], magic[1], magic[2], magic[3], magic[4], magic[5], magic[6], magic[7]] {
286            #[cfg(feature = "archive")]
287            [b'!', b'<', b'a', b'r', b'c', b'h', b'>', b'\n']
288            | [b'!', b'<', b't', b'h', b'i', b'n', b'>', b'\n'] => FileKind::Archive,
289            #[cfg(feature = "macho")]
290            [b'd', b'y', b'l', b'd', b'_', b'v', b'1', b' '] => FileKind::DyldCache,
291            #[cfg(feature = "elf")]
292            [0x7f, b'E', b'L', b'F', 1, ..] => FileKind::Elf32,
293            #[cfg(feature = "elf")]
294            [0x7f, b'E', b'L', b'F', 2, ..] => FileKind::Elf64,
295            #[cfg(feature = "macho")]
296            [0xfe, 0xed, 0xfa, 0xce, ..]
297            | [0xce, 0xfa, 0xed, 0xfe, ..] => FileKind::MachO32,
298            #[cfg(feature = "macho")]
299            | [0xfe, 0xed, 0xfa, 0xcf, ..]
300            | [0xcf, 0xfa, 0xed, 0xfe, ..] => FileKind::MachO64,
301            #[cfg(feature = "macho")]
302            [0xca, 0xfe, 0xba, 0xbe, ..] => FileKind::MachOFat32,
303            #[cfg(feature = "macho")]
304            [0xca, 0xfe, 0xba, 0xbf, ..] => FileKind::MachOFat64,
305            #[cfg(feature = "wasm")]
306            [0x00, b'a', b's', b'm', _, _, 0x00, 0x00] => FileKind::Wasm,
307            #[cfg(feature = "pe")]
308            [b'M', b'Z', ..] if offset == 0 => {
309                // offset == 0 restriction is because optional_header_magic only looks at offset 0
310                match pe::optional_header_magic(data) {
311                    Ok(crate::pe::IMAGE_NT_OPTIONAL_HDR32_MAGIC) => {
312                        FileKind::Pe32
313                    }
314                    Ok(crate::pe::IMAGE_NT_OPTIONAL_HDR64_MAGIC) => {
315                        FileKind::Pe64
316                    }
317                    _ => return Err(Error("Unknown MS-DOS file")),
318                }
319            }
320            // TODO: more COFF machines
321            #[cfg(feature = "coff")]
322            // COFF arm
323            [0xc4, 0x01, ..]
324            // COFF arm64
325            | [0x64, 0xaa, ..]
326            // COFF arm64ec
327            | [0x41, 0xa6, ..]
328            // COFF ppc
329            | [0xf0, 0x01, ..]
330            | [0xf1, 0x01, ..]
331            | [0xf2, 0x01, ..]
332            // COFF x86
333            | [0x4c, 0x01, ..]
334            // COFF x86-64
335            | [0x64, 0x86, ..] => FileKind::Coff,
336            #[cfg(feature = "coff")]
337            [0x00, 0x00, 0xff, 0xff, 0x00, 0x00, ..] => FileKind::CoffImport,
338            #[cfg(feature = "coff")]
339            [0x00, 0x00, 0xff, 0xff, 0x02, 0x00, ..] if offset == 0 => {
340                // offset == 0 restriction is because anon_object_class_id only looks at offset 0
341                match coff::anon_object_class_id(data) {
342                    Ok(crate::pe::ANON_OBJECT_HEADER_BIGOBJ_CLASS_ID) => FileKind::CoffBig,
343                    _ => return Err(Error("Unknown anon object file")),
344                }
345            }
346            #[cfg(feature = "xcoff")]
347            [0x01, 0xdf, ..] => FileKind::Xcoff32,
348            #[cfg(feature = "xcoff")]
349            [0x01, 0xf7, ..] => FileKind::Xcoff64,
350            _ => return Err(Error("Unknown file magic")),
351        };
352        Ok(kind)
353    }
354}
355
356/// An object kind.
357///
358/// Returned by [`Object::kind`].
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
360#[non_exhaustive]
361pub enum ObjectKind {
362    /// The object kind is unknown.
363    Unknown,
364    /// Relocatable object.
365    Relocatable,
366    /// Executable.
367    Executable,
368    /// Dynamic shared object.
369    Dynamic,
370    /// Core.
371    Core,
372}
373
374/// The index used to identify a section in a file.
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
376pub struct SectionIndex(pub usize);
377
378impl fmt::Display for SectionIndex {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        self.0.fmt(f)
381    }
382}
383
384/// The index used to identify a symbol in a symbol table.
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
386pub struct SymbolIndex(pub usize);
387
388impl fmt::Display for SymbolIndex {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        self.0.fmt(f)
391    }
392}
393
394/// The section where an [`ObjectSymbol`] is defined.
395#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
396#[non_exhaustive]
397pub enum SymbolSection {
398    /// The section is unknown.
399    Unknown,
400    /// The section is not applicable for this symbol (such as file symbols).
401    None,
402    /// The symbol is undefined.
403    Undefined,
404    /// The symbol has an absolute value.
405    Absolute,
406    /// The symbol is a zero-initialized symbol that will be combined with duplicate definitions.
407    Common,
408    /// The symbol is defined in the given section.
409    Section(SectionIndex),
410}
411
412impl SymbolSection {
413    /// Returns the section index for the section where the symbol is defined.
414    ///
415    /// May return `None` if the symbol is not defined in a section.
416    #[inline]
417    pub fn index(self) -> Option<SectionIndex> {
418        if let SymbolSection::Section(index) = self {
419            Some(index)
420        } else {
421            None
422        }
423    }
424}
425
426/// An imported symbol.
427///
428/// Returned by [`Object::imports`].
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
430pub struct Import<'data> {
431    library: ByteString<'data>,
432    // TODO: or ordinal
433    name: ByteString<'data>,
434}
435
436impl<'data> Import<'data> {
437    /// The symbol name.
438    #[inline]
439    pub fn name(&self) -> &'data [u8] {
440        self.name.0
441    }
442
443    /// The name of the library to import the symbol from.
444    #[inline]
445    pub fn library(&self) -> &'data [u8] {
446        self.library.0
447    }
448}
449
450/// An exported symbol.
451///
452/// Returned by [`Object::exports`].
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454pub struct Export<'data> {
455    // TODO: and ordinal?
456    name: ByteString<'data>,
457    address: u64,
458}
459
460impl<'data> Export<'data> {
461    /// The symbol name.
462    #[inline]
463    pub fn name(&self) -> &'data [u8] {
464        self.name.0
465    }
466
467    /// The virtual address of the symbol.
468    #[inline]
469    pub fn address(&self) -> u64 {
470        self.address
471    }
472}
473
474/// PDB information from the debug directory in a PE file.
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub struct CodeView<'data> {
477    guid: [u8; 16],
478    path: ByteString<'data>,
479    age: u32,
480}
481
482impl<'data> CodeView<'data> {
483    /// The path to the PDB as stored in CodeView.
484    #[inline]
485    pub fn path(&self) -> &'data [u8] {
486        self.path.0
487    }
488
489    /// The age of the PDB.
490    #[inline]
491    pub fn age(&self) -> u32 {
492        self.age
493    }
494
495    /// The GUID of the PDB.
496    #[inline]
497    pub fn guid(&self) -> [u8; 16] {
498        self.guid
499    }
500}
501
502/// The target referenced by a [`Relocation`].
503#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
504#[non_exhaustive]
505pub enum RelocationTarget {
506    /// The target is a symbol.
507    Symbol(SymbolIndex),
508    /// The target is a section.
509    Section(SectionIndex),
510    /// The offset is an absolute address.
511    Absolute,
512}
513
514/// A relocation entry.
515///
516/// Returned by [`Object::dynamic_relocations`] or [`ObjectSection::relocations`].
517pub struct Relocation {
518    kind: RelocationKind,
519    encoding: RelocationEncoding,
520    size: u8,
521    target: RelocationTarget,
522    subtractor: Option<SymbolIndex>,
523    addend: i64,
524    implicit_addend: bool,
525    flags: RelocationFlags,
526}
527
528impl fmt::Debug for Relocation {
529    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530        let mut s = f.debug_struct("Relocation");
531        s.field("kind", &self.kind)
532            .field("encoding", &self.encoding)
533            .field("size", &self.size)
534            .field("target", &self.target);
535        if let Some(subtractor) = self.subtractor {
536            s.field("subtractor", &subtractor);
537        }
538        s.field("addend", &self.addend)
539            .field("implicit_addend", &self.implicit_addend)
540            .field("flags", &self.flags)
541            .finish()
542    }
543}
544
545impl Relocation {
546    /// The operation used to calculate the result of the relocation.
547    #[inline]
548    pub fn kind(&self) -> RelocationKind {
549        self.kind
550    }
551
552    /// Information about how the result of the relocation operation is encoded in the place.
553    #[inline]
554    pub fn encoding(&self) -> RelocationEncoding {
555        self.encoding
556    }
557
558    /// The size in bits of the place of the relocation.
559    ///
560    /// If 0, then the size is determined by the relocation kind.
561    #[inline]
562    pub fn size(&self) -> u8 {
563        self.size
564    }
565
566    /// The target of the relocation.
567    #[inline]
568    pub fn target(&self) -> RelocationTarget {
569        self.target
570    }
571
572    /// A subtractor symbol.
573    ///
574    /// The relocation calculation subtracts the value of this symbol, if any.
575    #[inline]
576    pub fn subtractor(&self) -> Option<SymbolIndex> {
577        self.subtractor
578    }
579
580    /// The addend to use in the relocation calculation.
581    #[inline]
582    pub fn addend(&self) -> i64 {
583        self.addend
584    }
585
586    /// Set the addend to use in the relocation calculation.
587    #[inline]
588    pub fn set_addend(&mut self, addend: i64) {
589        self.addend = addend;
590    }
591
592    /// Returns true if there is an implicit addend stored in the data at the offset
593    /// to be relocated.
594    #[inline]
595    pub fn has_implicit_addend(&self) -> bool {
596        self.implicit_addend
597    }
598
599    /// Relocation flags that are specific to each file format.
600    ///
601    /// The values returned by `kind`, `encoding` and `size` are derived
602    /// from these flags.
603    #[inline]
604    pub fn flags(&self) -> RelocationFlags {
605        self.flags
606    }
607}
608
609/// A map from section offsets to relocation information.
610///
611/// This can be used to apply relocations to a value at a given section offset.
612/// This is intended for use with DWARF in relocatable object files, and only
613/// supports relocations that are used in DWARF.
614///
615/// Returned by [`ObjectSection::relocation_map`].
616#[derive(Debug, Default)]
617pub struct RelocationMap(Map<u64, RelocationMapEntry>);
618
619impl RelocationMap {
620    /// Construct a new relocation map for a section.
621    ///
622    /// Fails if any relocation cannot be added to the map.
623    /// You can manually use `add` if you need different error handling,
624    /// such as to list all errors or to ignore them.
625    pub fn new<'data, 'file, T>(file: &'file T, section: &T::Section<'file>) -> Result<Self>
626    where
627        T: Object<'data>,
628    {
629        let mut map = RelocationMap(Map::new());
630        for (offset, relocation) in section.relocations() {
631            map.add(file, offset, relocation)?;
632        }
633        Ok(map)
634    }
635
636    /// Add a single relocation to the map.
637    pub fn add<'data: 'file, 'file, T>(
638        &mut self,
639        file: &'file T,
640        offset: u64,
641        relocation: Relocation,
642    ) -> Result<()>
643    where
644        T: Object<'data>,
645    {
646        let mut entry = RelocationMapEntry {
647            implicit_addend: relocation.has_implicit_addend(),
648            addend: relocation.addend() as u64,
649        };
650        match relocation.kind() {
651            RelocationKind::None => return Ok(()),
652            RelocationKind::Absolute => match relocation.target() {
653                RelocationTarget::Symbol(symbol_idx) => {
654                    let symbol = file
655                        .symbol_by_index(symbol_idx)
656                        .read_error("Relocation with invalid symbol")?;
657                    entry.addend = symbol.address().wrapping_add(entry.addend);
658                }
659                RelocationTarget::Section(section_idx) => {
660                    let section = file
661                        .section_by_index(section_idx)
662                        .read_error("Relocation with invalid section")?;
663                    // DWARF parsers expect references to DWARF sections to be section offsets,
664                    // not addresses. Addresses are useful for everything else.
665                    if section.kind() != SectionKind::Debug {
666                        entry.addend = section.address().wrapping_add(entry.addend);
667                    }
668                }
669                _ => {
670                    return Err(Error("Unsupported relocation target"));
671                }
672            },
673            _ => {
674                return Err(Error("Unsupported relocation type"));
675            }
676        }
677        if relocation.encoding() != RelocationEncoding::Generic {
678            return Err(Error("Unsupported relocation encoding"));
679        }
680        if self.0.insert(offset, entry).is_some() {
681            return Err(Error("Multiple relocations for offset"));
682        }
683        Ok(())
684    }
685
686    /// Relocate a value that was read from the section at the given offset.
687    pub fn relocate(&self, offset: u64, value: u64) -> u64 {
688        if let Some(relocation) = self.0.get(&offset) {
689            if relocation.implicit_addend {
690                // Use the explicit addend too, because it may have the symbol value.
691                value.wrapping_add(relocation.addend)
692            } else {
693                relocation.addend
694            }
695        } else {
696            value
697        }
698    }
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
702struct RelocationMapEntry {
703    implicit_addend: bool,
704    addend: u64,
705}
706
707/// A data compression format.
708#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
709#[non_exhaustive]
710pub enum CompressionFormat {
711    /// The data is uncompressed.
712    None,
713    /// The data is compressed, but the compression format is unknown.
714    Unknown,
715    /// ZLIB/DEFLATE.
716    ///
717    /// Used for ELF compression and GNU compressed debug information.
718    Zlib,
719    /// Zstandard.
720    ///
721    /// Used for ELF compression.
722    Zstandard,
723}
724
725/// A range in a file that may be compressed.
726///
727/// Returned by [`ObjectSection::compressed_file_range`].
728#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
729pub struct CompressedFileRange {
730    /// The data compression format.
731    pub format: CompressionFormat,
732    /// The file offset of the compressed data.
733    pub offset: u64,
734    /// The compressed data size.
735    pub compressed_size: u64,
736    /// The uncompressed data size.
737    pub uncompressed_size: u64,
738}
739
740impl CompressedFileRange {
741    /// Data that is uncompressed.
742    #[inline]
743    pub fn none(range: Option<(u64, u64)>) -> Self {
744        if let Some((offset, size)) = range {
745            CompressedFileRange {
746                format: CompressionFormat::None,
747                offset,
748                compressed_size: size,
749                uncompressed_size: size,
750            }
751        } else {
752            CompressedFileRange {
753                format: CompressionFormat::None,
754                offset: 0,
755                compressed_size: 0,
756                uncompressed_size: 0,
757            }
758        }
759    }
760
761    /// Convert to [`CompressedData`] by reading from the file.
762    pub fn data<'data, R: ReadRef<'data>>(self, file: R) -> Result<CompressedData<'data>> {
763        let data = file
764            .read_bytes_at(self.offset, self.compressed_size)
765            .read_error("Invalid compressed data size or offset")?;
766        Ok(CompressedData {
767            format: self.format,
768            data,
769            uncompressed_size: self.uncompressed_size,
770        })
771    }
772}
773
774/// Data that may be compressed.
775///
776/// Returned by [`ObjectSection::compressed_data`].
777#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
778pub struct CompressedData<'data> {
779    /// The data compression format.
780    pub format: CompressionFormat,
781    /// The compressed data.
782    pub data: &'data [u8],
783    /// The uncompressed data size.
784    pub uncompressed_size: u64,
785}
786
787impl<'data> CompressedData<'data> {
788    /// Data that is uncompressed.
789    #[inline]
790    pub fn none(data: &'data [u8]) -> Self {
791        CompressedData {
792            format: CompressionFormat::None,
793            data,
794            uncompressed_size: data.len() as u64,
795        }
796    }
797
798    /// Return the uncompressed data.
799    ///
800    /// Returns an error for invalid data or unsupported compression.
801    /// This includes if the data is compressed but the `compression` feature
802    /// for this crate is disabled.
803    pub fn decompress(self) -> Result<Cow<'data, [u8]>> {
804        match self.format {
805            CompressionFormat::None => Ok(Cow::Borrowed(self.data)),
806            #[cfg(feature = "compression")]
807            CompressionFormat::Zlib | CompressionFormat::Zstandard => {
808                use alloc::vec::Vec;
809                use core::convert::TryInto;
810                use std::io::Read;
811                let size = self
812                    .uncompressed_size
813                    .try_into()
814                    .ok()
815                    .read_error("Uncompressed data size is too large.")?;
816                let mut decompressed = Vec::new();
817                decompressed
818                    .try_reserve_exact(size)
819                    .ok()
820                    .read_error("Uncompressed data allocation failed")?;
821
822                match self.format {
823                    CompressionFormat::Zlib => {
824                        let mut decompress = flate2::Decompress::new(true);
825                        decompress
826                            .decompress_vec(
827                                self.data,
828                                &mut decompressed,
829                                flate2::FlushDecompress::Finish,
830                            )
831                            .ok()
832                            .read_error("Invalid zlib compressed data")?;
833                    }
834                    CompressionFormat::Zstandard => {
835                        let mut input = self.data;
836                        while !input.is_empty() {
837                            let mut decoder = match ruzstd::decoding::StreamingDecoder::new(&mut input) {
838                                Ok(decoder) => decoder,
839                                Err(
840                                    ruzstd::decoding::errors::FrameDecoderError::ReadFrameHeaderError(
841                                        ruzstd::decoding::errors::ReadFrameHeaderError::SkipFrame {
842                                            length,
843                                            ..
844                                        },
845                                    ),
846                                ) => {
847                                    input = input
848                                        .get(length as usize..)
849                                        .read_error("Invalid zstd compressed data")?;
850                                    continue;
851                                }
852                                x => x.ok().read_error("Invalid zstd compressed data")?,
853                            };
854                            decoder
855                                .read_to_end(&mut decompressed)
856                                .ok()
857                                .read_error("Invalid zstd compressed data")?;
858                        }
859                    }
860                    _ => unreachable!(),
861                }
862                if size != decompressed.len() {
863                    return Err(Error(
864                        "Uncompressed data size does not match compression header",
865                    ));
866                }
867
868                Ok(Cow::Owned(decompressed))
869            }
870            _ => Err(Error("Unsupported compressed data.")),
871        }
872    }
873}