[
  {
    "path": ".dockerignore",
    "content": "target\nDockerfile\nbuild_docker.sh\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text eol=lf\n*.md text eol=lf\n*.png binary\n*.bin binary\n"
  },
  {
    "path": ".github/workflows/quality.yaml",
    "content": "name: Quality Checks\non:\n  pull_request:\n    branches:\n      - \"*\"\n\njobs:\n  lychee-link-check:\n    name: Link check\n    runs-on: ubuntu-latest\n    steps:\n      - name: Code checkout\n        uses: actions/checkout@v4\n\n      - name: Link Checker\n        uses: lycheeverse/lychee-action@v1.8.0\n        with:\n          fail: true\n\n  fmt:\n    name: Formatting (rustfmt)\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - target: x86_64-unknown-linux-gnu\n\n    steps:\n      - name: Code checkout\n        uses: actions/checkout@v4\n\n      - name: Install Rust toolchain (stable)\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          target: ${{ matrix.platform.target }}\n          components: rustfmt\n\n      - name: Formatting (rustfmt)\n        run: cargo fmt -- --check\n\n  lint:\n    name: Lint (clippy)\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - target: x86_64-unknown-linux-gnu\n\n    steps:\n      - name: Code checkout\n        uses: actions/checkout@v4\n\n      - name: Install Rust toolchain (stable)\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          target: ${{ matrix.platform.target }}\n          components: clippy\n\n      - name: Clippy (all crates)\n        run: cargo clippy --locked --target=${{ matrix.platform.target }} --workspace --all-targets -- -D warnings\n\n      - name: Check build did not modify any files\n        run: test -z \"$(git status --porcelain)\"\n"
  },
  {
    "path": ".github/workflows/rust-release-binary.yml",
    "content": "name: Release Build\n\non:\n  push:\n    tags:\n      - 'v*'\njobs:\n  release:\n    name: Release - ${{ matrix.platform.os-name }}\n    strategy:\n      matrix:\n        platform:\n          - os-name: Linux-x86_64\n            runs-on: ubuntu-24.04\n            target: x86_64-unknown-linux-gnu\n\n          - os-name: Linux-aarch64\n            runs-on: ubuntu-24.04-arm\n            target: aarch64-unknown-linux-gnu\n\n          - os-name: macOS-x86_64\n            runs-on: macOS-latest\n            target: x86_64-apple-darwin\n\n          - os-name: macOS-aarch64\n            runs-on: macOS-latest\n            target: aarch64-apple-darwin\n\n          - os-name: Windows-x86_64\n            runs-on: windows-latest\n            target: x86_64-pc-windows-msvc\n\n          - os-name: Windows-aarch64\n            runs-on: windows-11-arm\n            target: aarch64-pc-windows-msvc\n    \n    runs-on: ${{ matrix.platform.runs-on }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      \n      - name: Update CHANGELOG\n        if: startsWith(github.ref, 'refs/tags/')\n        id: changelog\n        uses: requarks/changelog-action@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          tag: ${{ github.ref_name }}\n          writeToFile: true\n          changelogFilePath: \"CHANGELOG.md\"\n\n      - name: Build binary\n        uses: houseabsolute/actions-rust-cross@v1\n        with:\n          command: build\n          target: ${{ matrix.platform.target }}\n          args: \"--locked --release\"\n          strip: true\n\n      - name: Publish artifacts and release\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: houseabsolute/actions-rust-release@v0\n        with:\n          executable-name: binwalk\n          target: ${{ matrix.platform.target }}\n          release-tag-prefix: v\n          changes-file: \"CHANGELOG.md\"          \n          action-gh-release-parameters: |\n            {\n              \"draft\": false,\n              \"prerelease\": false,\n              \"overwrite\": true,\n              \"generate_release_notes\": true,\n              \"make_latest\": true,\n              \"body_path\": \"CHANGELOG.md\",\n              \"token\": \"${{ secrets.GITHUB_TOKEN }}\"\n            }\n      \n      - name: Commit CHANGELOG.md\n        if: startsWith(github.ref, 'refs/tags/') && matrix.platform.os-name == 'Linux-x86_64'\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          branch: master\n          commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'\n          file_pattern: CHANGELOG.md\n        \n"
  },
  {
    "path": ".gitignore",
    "content": "target\n"
  },
  {
    "path": "CARGO_README.md",
    "content": "# binwalk\n\nA Rust implementation of the Binwalk firmware analysis tool.\n\n## System Requirements\n\nBuilding requires the following system packages:\n\n```\nbuild-essential libfontconfig1-dev liblzma-dev\n```\n\n## Example\n\n```\nuse binwalk::Binwalk;\n\n// Create a new Binwalk instance\nlet binwalker = Binwalk::new();\n\n// Read in the data to analyze\nlet file_data = std::fs::read(\"/tmp/firmware.bin\").expect(\"Failed to read from file\");\n\n// Scan the file data and print the results\nfor result in binwalker.scan(&file_data) {\n    println!(\"{:#?}\", result);\n}\n```\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"binwalk\"\nversion = \"3.1.1\"\nedition = \"2024\"\nauthors = [\"Craig Heffner <heffnercj@gmail.com>\"]\nlicense = \"MIT\"\nreadme = \"CARGO_README.md\"\nrepository = \"https://github.com/ReFirmLabs/binwalk\"\ndescription = \"Analyzes data for embedded file types\"\nkeywords = [\"binwalk\", \"firmware\", \"analysis\"]\n\n[dependencies]\nlog = \"0.4.22\"\nbase64 = \"0.22.1\"\nchrono = \"0.4.38\"\nwalkdir = \"2.5.0\"\nentropy = \"0.4.2\"\ncolored = \"3.0.0\"\ntermsize = \"0.1\"\ncrc32-v2 = \"0.0.5\"\ncrc32c = \"0.6.8\"\nliblzma = \"0.4.2\"\nbzip2 = \"0.6.0\"\nthreadpool = \"1.8.1\"\nserde_json = \"1.0\"\nenv_logger = \"0.11.5\"\nflate2 = \"1.1.2\"\nadler32 = \"1.2.0\"\nmd5 = \"0.8.0\"\nminiz_oxide = \"0.8.0\"\naho-corasick = \"1.1.3\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nclap = { version = \"4.5.16\", features = [\"derive\"] }\nxxhash-rust = { version = \"0.8.12\", features = [\"xxh32\"] }\nhex = \"0.4.3\"\ndelink = { git = \"https://github.com/devttys0/delink\" }\nplotly = { version = \"0.13.1\", features = [\"kaleido\", \"kaleido_download\"] }\n\n[dependencies.uuid]\nversion = \"1.17.0\"\nfeatures = [\n    \"v4\",                # Lets you generate random UUIDs\n    \"fast-rng\",          # Use a faster (but still sufficiently random) RNG\n    \"macro-diagnostics\", # Enable better diagnostics for compile-time UUIDs\n]\n\n[profile.release]\nlto = true\n"
  },
  {
    "path": "Dockerfile",
    "content": "## Scratch build stage\nFROM ubuntu:25.04 AS build\n\nARG BUILD_DIR=\"/tmp\"\nARG BINWALK_BUILD_DIR=\"${BUILD_DIR}/binwalk\"\nARG SASQUATCH_FILENAME=\"sasquatch_1.0.deb\"\nARG SASQUATCH_BASE_FILE_URL=\"https://github.com/onekey-sec/sasquatch/releases/download/sasquatch-v4.5.1-5/\"\n\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=Etc/UTC\n\nCOPY . ${BINWALK_BUILD_DIR}\nWORKDIR ${BINWALK_BUILD_DIR}\n\n# Pull build needs, build dumpifs, lzfse, dmg2img, vfdecrypt, and binwalk\n# Cleaning up our mess here doesn't matter, as anything generated in\n# this stage won't make it into the final image unless it's explicitly copied\nRUN apt-get update -y \\\n    && apt-get upgrade -y \\\n    && apt-get -y --no-install-recommends install \\\n    ca-certificates \\\n    tzdata \\\n    curl \\\n    git \\\n    wget \\\n    build-essential \\\n    clang \\\n    zlib1g \\\n    zlib1g-dev \\\n    liblz4-1 \\\n    libsrecord-dev \\\n    liblzma-dev \\\n    liblzo2-dev \\\n    libucl-dev \\\n    liblz4-dev \\\n    libbz2-dev \\\n    libssl-dev \\\n    pkg-config \\\n    && curl -L -o \"${SASQUATCH_FILENAME}\" \"${SASQUATCH_BASE_FILE_URL}\\sasquatch_1.0_$(dpkg --print-architecture).deb\" \\\n    && git clone https://github.com/askac/dumpifs.git ${BUILD_DIR}/dumpifs \\\n    && git clone https://github.com/lzfse/lzfse.git ${BUILD_DIR}/lzfse \\\n    && git clone https://github.com/Lekensteyn/dmg2img.git ${BUILD_DIR}/dmg2img \\\n    && rm ${BUILD_DIR}/dumpifs/dumpifs \\\n    && make -C ${BUILD_DIR}/dumpifs dumpifs \\\n    && make -C ${BUILD_DIR}/lzfse install \\\n    && make -C ${BUILD_DIR}/dmg2img dmg2img vfdecrypt HAVE_LZFSE=1 \\\n    && curl https://sh.rustup.rs -sSf | sh -s -- -y \\\n    && . /root/.cargo/env \\\n    && cargo build --release\n\n\n## Prod image build stage\nFROM ubuntu:25.04\n\nARG BUILD_DIR=\"/tmp\"\nARG BINWALK_BUILD_DIR=\"${BUILD_DIR}/binwalk\"\nARG DEFAULT_WORKING_DIR=\"/analysis\"\nARG SASQUATCH_FILENAME=\"sasquatch_1.0.deb\"\n\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=Etc/UTC\n\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\nENV UV_SYSTEM_PYTHON=1 UV_BREAK_SYSTEM_PACKAGES=1\n\nWORKDIR ${BUILD_DIR}\n\n# Copy the build artifacts from the scratch build stage\nCOPY --from=build ${BINWALK_BUILD_DIR}/${SASQUATCH_FILENAME} ${BUILD_DIR}/${SASQUATCH_FILENAME}\nCOPY --from=build /usr/local/bin/lzfse ${BUILD_DIR}/dumpifs/dumpifs ${BUILD_DIR}/dmg2img/dmg2img ${BUILD_DIR}/dmg2img/vfdecrypt ${BINWALK_BUILD_DIR}/target/release/binwalk /usr/local/bin/\n\n# Install dependencies, create default working directory, and remove clang & friends.\n# clang is needed to build python-lzo and vmlinux-to-elf, but it's not needed\n# afterward, so it's safe to remove and reduces the image size by ~400MB.\n# Those two packages could be built in the scratch stage and copied over from it,\n# but that would require that I untangle the Eldritch Horror that is the\n# pip build process, and that's not a particular monster that I'm up to slaying today.\nRUN apt-get update -y \\\n    && apt-get upgrade -y \\\n    && apt-get -y install --no-install-recommends \\\n    ca-certificates \\\n    tzdata \\\n    python3 \\\n    7zip \\\n    zstd \\\n    srecord \\\n    tar \\\n    unzip \\\n    sleuthkit \\\n    cabextract \\\n    curl \\\n    wget \\\n    git \\\n    lz4 \\\n    lzop \\\n    unrar \\\n    unyaffs \\\n    zlib1g \\\n    zlib1g-dev \\\n    liblz4-1 \\\n    libsrecord-dev \\\n    liblzma-dev \\\n    liblzo2-dev \\\n    libucl-dev \\\n    liblz4-dev \\\n    libbz2-dev \\\n    libssl-dev \\\n    libfontconfig1-dev \\\n    libpython3-dev \\\n    7zip-standalone \\\n    cpio \\\n    device-tree-compiler \\\n    clang \\\n    && dpkg -i ${BUILD_DIR}/${SASQUATCH_FILENAME} \\\n    && rm ${BUILD_DIR}/${SASQUATCH_FILENAME} \\\n    && CC=clang uv pip install uefi_firmware jefferson ubi-reader git+https://github.com/marin-m/vmlinux-to-elf \\\n    && uv cache clean \\\n    && apt-get purge clang -y \\\n    && apt autoremove -y \\\n    && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* /bin/uv /bin/uvx \\\n    && mkdir -p ${DEFAULT_WORKING_DIR} \\\n    && chmod 777 ${DEFAULT_WORKING_DIR}\n\n\nWORKDIR ${DEFAULT_WORKING_DIR}\n\n# Run as the default ubuntu user\nUSER ubuntu\n\n# Enable this environment variable to remove extractor top-level symlink,\n# as the symlink target path in the docker environment will not match that of the host.\nENV BINWALK_RM_EXTRACTION_SYMLINK=1\n\nENTRYPOINT [ \"binwalk\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 devttys0\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Binwalk v3\n\nThis is an updated version of the Binwalk firmware analysis tool, re-written in Rust for speed and accuracy.\n\n![binwalk v3](images/binwalk_animated.svg)\n\n## What does it do?\n\nBinwalk can identify, and optionally extract, files and data that have been embedded inside of other files.\n\nWhile its primary focus is firmware analysis, it supports a [wide variety](https://github.com/ReFirmLabs/binwalk/wiki/Supported-Signatures) of file and data types.\n\nThrough [entropy analysis](https://github.com/ReFirmLabs/binwalk/wiki/Generating-Entropy-Graphs), it can even help to identify unknown compression or encryption!\n\nBinwalk can be customized and [integrated](https://github.com/ReFirmLabs/binwalk/wiki/Using-the-Rust-Library) into your own Rust projects.\n\n## How do I get it?\n\nThe easiest way to install Binwalk and all dependencies is to [build a Docker image](https://github.com/ReFirmLabs/binwalk/wiki/Building-A-Binwalk-Docker-Image).\n\nBinwalk can also be [installed](https://github.com/ReFirmLabs/binwalk/wiki/Cargo-Installation) via the Rust package manager.\n\nOr, you can [compile from source](https://github.com/ReFirmLabs/binwalk/wiki/Compile-From-Source)!\n\n## How do I use it?\n\nUsage is _**simple**_, analysis is _**fast**_, and results are _**detailed**_:\n\n```\nbinwalk DIR-890L_AxFW110b07.bin\n```\n![example output](images/output.png)\n\nUse `--help`, or check out the [Wiki](https://github.com/ReFirmLabs/binwalk/wiki#usage) for more advanced options!\n"
  },
  {
    "path": "build_docker.sh",
    "content": "#!/usr/bin/env bash\n\ndocker build --build-arg SCRIPT_DIRECTORY=$PWD -t binwalkv3 .\n\n"
  },
  {
    "path": "dependencies/README.md",
    "content": "# Binwalk Dependencies\n\nThese scripts install the required Binwalk build and runtime system dependencies, except for the Rust compiler itself.\n\nExecute the appropriate script for your operating system (e.g., `ubuntu.sh` for Ubuntu).\n\n## ubuntu.sh\n\nThis script installs *all* required dependencies for Ubuntu-based systems, including the dependencies listed in `pip.sh` and `src.sh`.\n\nThis should work for most Debian / Debian-based systems as well, but is only tested on Ubuntu.\n\n## pip.sh\n\nThis script installs all Python-based dependencies via `pip3`.\n\nIt should be sourced by higher-level scripts (e.g., `ubuntu.sh`).\n\n## src.sh\n\nThis script builds and installs all source-based dependencies.\n\nIt should be sourced by higher-level scripts (e.g., `ubuntu.sh`).\n"
  },
  {
    "path": "dependencies/pip.sh",
    "content": "#!/bin/bash\n# Install pip dependencies.\n# Requires that pip3 is already installed.\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nif command -v uv >/dev/null 2>&1; then\n    uv pip install -r \"$SCRIPT_DIR/requirements.txt\"\nelse\n    pip install -r \"$SCRIPT_DIR/requirements.txt\" --break-system-packages\nfi\n"
  },
  {
    "path": "dependencies/requirements.txt",
    "content": "uefi_firmware\njefferson\nubi-reader\nlz4\nzstandard\ngit+https://github.com/marin-m/vmlinux-to-elf\n"
  },
  {
    "path": "dependencies/src.sh",
    "content": "#!/bin/bash\n# Install dependencies from source.\n# Requires that git and build tools (make, gcc, etc) are already installed.\n\n# Install dumpifs\ncd /tmp\ngit clone https://github.com/askac/dumpifs.git\ncd /tmp/dumpifs\nmake dumpifs\ncp ./dumpifs /usr/local/bin/dumpifs\ncd /tmp\nrm -rf /tmp/dumpifs\n\n\n# Install LZFSE utility and library\ncd /tmp\ngit clone https://github.com/lzfse/lzfse.git\ncd /tmp/lzfse\nmake install\ncd /tmp\nrm -rf /tmp/lzfse\n\n\n# Install dmg2img with LZFSE support\ncd /tmp\ngit clone https://github.com/Lekensteyn/dmg2img.git\ncd /tmp/dmg2img\nmake dmg2img HAVE_LZFSE=1\nmake install\ncd /tmp\nrm -rf /tmp/dmg2img\n"
  },
  {
    "path": "dependencies/ubuntu.sh",
    "content": "#!/bin/bash\n\n# Get the path to this script's directory, regardless of where it is run from\nSCRIPT_DIRECTORY=$(dirname -- \"$( readlink -f -- \"$0\"; )\")\n\n# Install dependencies from apt repository\nDEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install \\\n    7zip \\\n    zstd \\\n    srecord \\\n    tar \\\n    unzip \\\n    sleuthkit \\\n    cabextract \\\n    curl \\\n    wget \\\n    git \\\n    lz4 \\\n    lzop \\\n    unrar \\\n    unyaffs \\\n    python3-pip \\\n    build-essential \\\n    clang \\\n    liblzo2-dev \\\n    libucl-dev \\\n    liblz4-dev \\\n    libbz2-dev \\\n    zlib1g-dev \\\n    libfontconfig1-dev \\\n    liblzma-dev \\\n    libssl-dev \\\n    7zip-standalone \\\n    cpio \\\n    device-tree-compiler\n\n# Install sasquatch Debian package\ncurl -L -o sasquatch_1.0.deb \"https://github.com/onekey-sec/sasquatch/releases/download/sasquatch-v4.5.1-5/sasquatch_1.0_$(dpkg --print-architecture).deb\"\ndpkg -i sasquatch_1.0.deb\nrm sasquatch_1.0.deb\n\n# Install Python dependencies\nsource \"${SCRIPT_DIRECTORY}/pip.sh\"\n\n# Install dependencies from source\nsource \"${SCRIPT_DIRECTORY}/src.sh\"\n"
  },
  {
    "path": "fuzzing/Cargo.toml",
    "content": "[package]\nname = \"fuzz\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nafl = \"*\"\nbinwalk = { path = \"../\" }\n"
  },
  {
    "path": "fuzzing/README.md",
    "content": "# Fuzzing Binwalk\n\nFuzz testing for Binwalk is done through [AFL++](https://aflplus.plus).\n\nAt the moment code coverage is not 100% complete, but exercises the file parsing code, which is the most problematic and error-prone.\n\n## Fuzzer Dependencies\n\nYou must have a C compiler and `make` installed, as well as the `cargo-afl` crate:\n\n```\nsudo apt install build-essentials\ncargo install cargo-afl\n```\n\n## Building the Fuzzer\n\n```\ncargo afl build --release\n```\n\n## Running the Fuzzer\n\nYou must provide an input directory containing sample files for the fuzzer to mutate.\n\nYou must provide an output directory for the fuzzer to save crash results to.\n\n```\ncargo afl fuzz -i input_directory -o output_directory ./target/release/fuzz\n```\n"
  },
  {
    "path": "fuzzing/src/main.rs",
    "content": "use afl::fuzz;\nuse binwalk::Binwalk;\n\nfn main() {\n    // AFL makes this real simple...\n    fuzz!(|data: &[u8]| {\n        // Initialize binwalk, no extraction\n        let binwalker = Binwalk::new();\n        // Scan the data provided by AFL\n        binwalker.scan(&data.to_vec());\n    });\n}\n"
  },
  {
    "path": "scripts/binwalk-ui",
    "content": "#! /bin/bash -\n# https://unix.stackexchange.com/questions/290696/display-stdout-and-stderr-in-two-separate-streams\n# \n# This script will run binwalk in a split screen, displaying results in the top screen and debug output in the bottom screen.\n# The `screen` utility must be installed.\n\n# If BINWALK_PATH is not set, assume it is in the default cargo target path\nif [[ -z \"${BINWALK_PATH}\" ]]; then\n    BINWALK_PATH=\"$(cd \"$(dirname $0)\" && pwd)/../target/release/binwalk\"\nfi\n\n# If no RUST_LOG level is defined, default to `info`\nif [[ -z \"${RUST_LOG}\" ]]; then\n    export RUST_LOG=info\nfi\n\ntmpdir=$(mktemp -d) || exit\ntrap 'rm -rf \"$tmpdir\"' EXIT INT TERM HUP\n\nFIFO=$tmpdir/FIFO\nmkfifo \"$FIFO\" || exit\n\nconf=$tmpdir/conf\n\ncat > \"$conf\" << 'EOF' || exit\nsplit -h\nfocus\nscreen -t stderr sh -c 'tty > \"$FIFO\"; read done < \"$FIFO\"'\nfocus\nscreen -t stdout sh -c 'read tty < \"$FIFO\"; eval \"$CMD\" 2> \"$tty\"; echo \"[Command exited with status $?, press enter to exit]\"; read prompt; echo done > \"$FIFO\"'\nEOF\n\nCMD=\"$BINWALK_PATH $*\"\n\nexport FIFO CMD\n\nscreen -mc \"$conf\"\n"
  },
  {
    "path": "src/binwalk.rs",
    "content": "//! Primary Binwalk interface.\n\nuse aho_corasick::AhoCorasick;\nuse log::{debug, error, info, warn};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::fs;\nuse std::path;\nuse uuid::Uuid;\n\n#[cfg(windows)]\nuse std::os::windows;\n\n#[cfg(unix)]\nuse std::os::unix;\n\nuse crate::common::{is_offset_safe, read_file};\nuse crate::extractors;\nuse crate::magic;\nuse crate::signatures;\n\n/// Returned on initialization error\n#[derive(Debug, Default, Clone)]\npub struct BinwalkError {\n    pub message: String,\n}\n\nimpl BinwalkError {\n    pub fn new(message: &str) -> Self {\n        BinwalkError {\n            message: message.to_string(),\n        }\n    }\n}\n\n/// Analysis results returned by Binwalk::analyze\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct AnalysisResults {\n    /// Path to the file that was analyzed\n    pub file_path: String,\n    /// File signature results, as returned by Binwalk::scan\n    pub file_map: Vec<signatures::common::SignatureResult>,\n    /// File extraction results, as returned by Binwalk::extract.\n    /// HashMap key is the corresponding SignatureResult.id value in `file_map`.\n    pub extractions: HashMap<String, extractors::common::ExtractionResult>,\n}\n\n/// Analyze files / memory for file signatures\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::Binwalk;\n///\n/// let target_file = \"/bin/ls\";\n/// let data_to_scan = std::fs::read(target_file).expect(\"Unable to read file\");\n///\n/// let binwalker = Binwalk::new();\n///\n/// let signature_results = binwalker.scan(&data_to_scan);\n///\n/// for result in &signature_results {\n///     println!(\"Found '{}' at offset {:#X}\", result.description, result.offset);\n/// }\n/// ```\n#[derive(Debug, Default, Clone)]\npub struct Binwalk {\n    /// Count of all signatures (short and regular)\n    pub signature_count: usize,\n    /// Count of all magic patterns (short and regular)\n    pub pattern_count: usize,\n    /// The base file requested for analysis\n    pub base_target_file: String,\n    /// The base output directory for extracted files\n    pub base_output_directory: String,\n    /// A list of signatures that must start at offset 0\n    pub short_signatures: Vec<signatures::common::Signature>,\n    /// A list of magic bytes to search for throughout the entire file\n    pub patterns: Vec<Vec<u8>>,\n    /// Maps patterns to their corresponding signature\n    pub pattern_signature_table: HashMap<usize, signatures::common::Signature>,\n    /// Maps signatures to their corresponding extractors\n    pub extractor_lookup_table: HashMap<String, Option<extractors::common::Extractor>>,\n}\n\nimpl Binwalk {\n    /// Create a new Binwalk instance with all default values.\n    /// Equivalent to `Binwalk::configure(None, None, None, None, None, false)`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::Binwalk;\n    ///\n    /// let binwalker = Binwalk::new();\n    /// ```\n    #[allow(dead_code)]\n    pub fn new() -> Binwalk {\n        Binwalk::configure(None, None, None, None, None, false).unwrap()\n    }\n\n    /// Create a new Binwalk instance.\n    ///\n    /// If `target_file_name` and `output_directory` are specified, the `output_directory` will be created if it does not\n    /// already exist, and a symlink to `target_file_name` will be placed inside the `output_directory`. The path to this\n    /// symlink is placed in `Binwalk.base_target_file`.\n    ///\n    /// The `include` and `exclude` arguments specify include and exclude signature filters. The String values contained\n    /// in these arguments must match the `Signature.name` values defined in magic.rs.\n    ///\n    /// Additional user-defined signatures may be provided via the `signatures` argument.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_102_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {\n    /// use binwalk::Binwalk;\n    ///\n    /// // Don't scan for these file signatures\n    /// let exclude_filters: Vec<String> = vec![\"jpeg\".to_string(), \"png\".to_string()];\n    ///\n    /// let binwalker = Binwalk::configure(None,\n    ///                                    None,\n    ///                                    None,\n    ///                                    Some(exclude_filters),\n    ///                                    None,\n    ///                                    false)?;\n    /// # Ok(binwalker)\n    /// # } _doctest_main_src_binwalk_rs_102_0(); }\n    /// ```\n    pub fn configure(\n        target_file_name: Option<String>,\n        output_directory: Option<String>,\n        include: Option<Vec<String>>,\n        exclude: Option<Vec<String>>,\n        signatures: Option<Vec<signatures::common::Signature>>,\n        full_search: bool,\n    ) -> Result<Binwalk, BinwalkError> {\n        let mut new_instance = Binwalk {\n            ..Default::default()\n        };\n\n        // Target file is optional, especially if being called via the library\n        if let Some(target_file) = target_file_name {\n            // Set the target file path, make it an absolute path\n            match path::absolute(&target_file) {\n                Err(_) => {\n                    return Err(BinwalkError::new(&format!(\n                        \"Failed to get absolute path for '{target_file}'\"\n                    )));\n                }\n                Ok(abspath) => {\n                    new_instance.base_target_file = abspath.display().to_string();\n                }\n            }\n\n            // If an output extraction directory was also specified, initialize it\n            if let Some(extraction_directory) = output_directory {\n                // Make the extraction directory an absolute path\n                match path::absolute(&extraction_directory) {\n                    Err(_) => {\n                        return Err(BinwalkError::new(&format!(\n                            \"Failed to get absolute path for '{extraction_directory}'\"\n                        )));\n                    }\n                    Ok(abspath) => {\n                        new_instance.base_output_directory = abspath.display().to_string();\n                    }\n                }\n\n                // Initialize the extraction directory. This will create the directory if it\n                // does not exist, and create a symlink inside the directory that points to\n                // the specified target file.\n                match init_extraction_directory(\n                    &new_instance.base_target_file,\n                    &new_instance.base_output_directory,\n                ) {\n                    Err(e) => {\n                        return Err(BinwalkError::new(&format!(\n                            \"Failed to initialize extraction directory: {e}\"\n                        )));\n                    }\n                    Ok(new_target_file_path) => {\n                        // This is the new base target path (a symlink inside the extraction directory)\n                        new_instance.base_target_file = new_target_file_path.clone();\n                    }\n                }\n            }\n        }\n\n        // Load all internal signature patterns\n        let mut signature_patterns = magic::patterns();\n\n        // Include any user-defined signature patterns\n        if let Some(user_defined_signature_patterns) = signatures {\n            signature_patterns.extend(user_defined_signature_patterns);\n        }\n\n        // Load magic signatures\n        for signature in signature_patterns.clone() {\n            // Check if this signature should be included\n            if !include_signature(&signature, &include, &exclude) {\n                continue;\n            }\n\n            // Keep a count of total unique signatures that are supported\n            new_instance.signature_count += 1;\n\n            // Keep a count of the total number of magic patterns\n            new_instance.pattern_count += signature.magic.len();\n\n            // Create a lookup table which associates each signature to its respective extractor\n            new_instance\n                .extractor_lookup_table\n                .insert(signature.name.clone(), signature.extractor.clone());\n\n            // Each signature may have multiple magic bytes associated with it\n            for pattern in signature.magic.clone() {\n                if signature.short && !full_search {\n                    // These are short patterns, and should only be searched for at the very beginning of a file\n                    new_instance.short_signatures.push(signature.clone());\n                    break;\n                } else {\n                    /*\n                     * Need to keep a mapping of the pattern index and its associated signature\n                     * so that when a match is found it can be resolved back to the signature from\n                     * which it came.\n                     */\n                    new_instance\n                        .pattern_signature_table\n                        .insert(new_instance.patterns.len(), signature.clone());\n\n                    // Add these magic bytes to the list of patterns\n                    new_instance.patterns.push(pattern.to_vec());\n                }\n            }\n        }\n\n        Ok(new_instance)\n    }\n\n    /// Scan a file for magic signatures.\n    /// Returns a list of validated magic signatures representing the known contents of the file.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::Binwalk;\n    ///\n    /// let target_file = \"/bin/ls\";\n    /// let data_to_scan = std::fs::read(target_file).expect(\"Unable to read file\");\n    ///\n    /// let binwalker = Binwalk::new();\n    ///\n    /// let signature_results = binwalker.scan(&data_to_scan);\n    ///\n    /// for result in &signature_results {\n    ///     println!(\"{:#X}  {}\", result.offset, result.description);\n    /// }\n    ///\n    /// assert!(signature_results.len() > 0);\n    /// ```\n    pub fn scan(&self, file_data: &[u8]) -> Vec<signatures::common::SignatureResult> {\n        const FILE_START_OFFSET: usize = 0;\n\n        let mut index_adjustment: usize = 0;\n        let mut next_valid_offset: usize = 0;\n        let mut previous_valid_offset = None;\n\n        let available_data = file_data.len();\n\n        // A list of identified signatures, representing a \"map\" of the file data\n        let mut file_map: Vec<signatures::common::SignatureResult> = vec![];\n\n        /*\n         * Check beginning of file for short signatures.\n         * These signatures are only valid if they occur at the very beginning of a file.\n         * This is typically because the signatures are very short and they are likely\n         * to occur randomly throughout the file, so this prevents having to validate many\n         * false positve matches.\n         */\n        for signature in &self.short_signatures {\n            for magic in signature.magic.clone() {\n                let magic_start = FILE_START_OFFSET + signature.magic_offset;\n                let magic_end = magic_start + magic.len();\n\n                if file_data.len() > magic_end && file_data[magic_start..magic_end] == magic {\n                    debug!(\n                        \"Found {} short magic match at offset {:#X}\",\n                        signature.description, magic_start\n                    );\n\n                    if let Ok(mut signature_result) = (signature.parser)(file_data, magic_start) {\n                        // Auto populate some signature result fields\n                        signature_result_auto_populate(&mut signature_result, signature);\n\n                        // Add this signature to the file map\n                        file_map.push(signature_result.clone());\n                        info!(\n                            \"Found valid {} short signature at offset {:#X}\",\n                            signature_result.name, FILE_START_OFFSET\n                        );\n\n                        // Only update the next_valid_offset if confidence is high; these are, after all, short signatures\n                        if signature_result.confidence >= signatures::common::CONFIDENCE_HIGH {\n                            next_valid_offset = signature_result.offset + signature_result.size;\n                        }\n\n                        // Only one signature can match at fixed offset 0\n                        break;\n                    } else {\n                        debug!(\n                            \"{} short signature match at offset {:#X} is invalid\",\n                            signature.description, FILE_START_OFFSET\n                        );\n                    }\n                }\n            }\n        }\n\n        /*\n         * Same pattern matching algorithm used by fgrep.\n         * This will search for all magic byte patterns in the file data, all at once.\n         * https://en.wikipedia.org/wiki/Aho–Corasick_algorithm\n         */\n        let grep = AhoCorasick::new(self.patterns.clone()).unwrap();\n\n        debug!(\"Running Aho-Corasick scan\");\n\n        /*\n         * Outer loop wrapper for AhoCorasick scan loop. This will loop until:\n         *\n         *  1) next_valid_offset exceeds available_data\n         *  2) previous_valid_offset <= next_valid_offset\n         */\n        while is_offset_safe(available_data, next_valid_offset, previous_valid_offset) {\n            // Update the previous valid offset in praparation for the next loop iteration\n            previous_valid_offset = Some(next_valid_offset);\n\n            debug!(\"Continuing scan from offset {next_valid_offset:#X}\");\n\n            /*\n             * Run a new AhoCorasick scan starting at the next valid offset in the file data.\n             * This will loop until:\n             *\n             *  1) All data has been exhausted, in which case previous_valid_offset and next_valid_offset\n             *     will be identical, causing the outer while loop to break.\n             *  2) A valid signature with a defined size is found, in which case next_valid_offset will\n             *     be updated to point the end of the valid signature data, causing a new AhoCorasick\n             *     scan to start at the new next_valid_offset file location.\n             */\n            for magic_match in grep.find_overlapping_iter(&file_data[next_valid_offset..]) {\n                // Get the location of the magic bytes inside the file data\n                let magic_offset: usize = next_valid_offset + magic_match.start();\n\n                // Get the signature associated with this magic signature\n                let magic_pattern_index: usize = magic_match.pattern().as_usize();\n                let signature: signatures::common::Signature = self\n                    .pattern_signature_table\n                    .get(&magic_pattern_index)\n                    .unwrap()\n                    .clone();\n\n                debug!(\n                    \"Found {} magic match at offset {:#X}\",\n                    signature.description, magic_offset\n                );\n\n                /*\n                 * Invoke the signature parser to parse and validate the signature.\n                 * An error indicates a false positive match for the signature type.\n                 */\n                if let Ok(mut signature_result) = (signature.parser)(file_data, magic_offset) {\n                    // Calculate the end of this signature's data\n                    let signature_end_offset = signature_result.offset + signature_result.size;\n\n                    // Sanity check the reported offset and size vs file size\n                    if signature_end_offset > available_data {\n                        info!(\"Signature {} extends beyond EOF; ignoring\", signature.name);\n                        // Continue inner loop\n                        continue;\n                    }\n\n                    // Auto populate some signature result fields\n                    signature_result_auto_populate(&mut signature_result, &signature);\n\n                    // Add this signature to the file map\n                    file_map.push(signature_result.clone());\n\n                    info!(\n                        \"Found valid {} signature at offset {:#X}\",\n                        signature_result.name, signature_result.offset\n                    );\n\n                    // Only update the next_valid_offset if confidence is at least medium\n                    if signature_result.confidence >= signatures::common::CONFIDENCE_MEDIUM {\n                        // Only update the next_valid offset if the end of the signature reported the size of its contents\n                        if signature_result.size > 0 {\n                            // This file's signature has a known size, so there's no need to scan inside this file's data.\n                            // Update next_valid_offset to point to the end of this file signature and break out of the\n                            // inner loop.\n                            next_valid_offset = signature_end_offset;\n                            break;\n                        }\n                    }\n                } else {\n                    debug!(\n                        \"{} magic match at offset {:#X} is invalid\",\n                        signature.description, magic_offset\n                    );\n                }\n            }\n        }\n\n        debug!(\"Aho-Corasick scan found {} magic matches\", file_map.len());\n\n        /*\n         * A file's magic bytes do not always start at the beginning of a file, meaning that it is possible\n         * that the order in which the signatures were found in the file data is not the order in which we\n         * want to process/validate the signatures. Each signature's parser function will report the correct\n         * starting offset for the signature, so sort the file_map by the SignatureResult.offset value.\n         */\n        file_map.sort();\n        next_valid_offset = 0;\n\n        /*\n         * Now that signatures are in the correct order, identify and any overlapping signatures\n         * (such as gzip files identified within a tarball archive), signatures with the same reported offset,\n         * and any signatures with an invalid reported size (i.e., the size extends beyond the end of available file_data).\n         */\n        for mut i in 0..file_map.len() {\n            // Some entries may have been removed from the file_map list in previous loop iterations; adjust the index accordingly\n            i -= index_adjustment;\n\n            // Make sure the file map index is valid\n            if file_map.is_empty() || i >= file_map.len() {\n                break;\n            }\n\n            let this_signature = file_map[i].clone();\n            let remaining_available_size = file_data.len() - this_signature.offset;\n\n            // Check if the previous file map entry had the same reported starting offset as this one\n            if i > 0 && this_signature.offset == file_map[i - 1].offset {\n                // Get the previous signature in the file map\n                let previous_signature = file_map[i - 1].clone();\n\n                // If this file map entry and the conflicting entry do not have the same confidence level, default to the one with highest confidence\n                if this_signature.confidence != previous_signature.confidence {\n                    debug!(\n                        \"Conflicting signatures at offset {:#X}; defaulting to the signature with highest confidence\",\n                        this_signature.offset\n                    );\n\n                    // If this signature is higher confidence, invalidate the previous signature\n                    if this_signature.confidence > previous_signature.confidence {\n                        file_map.remove(i - 1);\n                        index_adjustment += 1;\n\n                    // Else, this signature has a lower confidence; invalidate this signature and continue to the next signature in the list\n                    } else {\n                        file_map.remove(i);\n                        index_adjustment += 1;\n                        continue;\n                    }\n\n                // Conflicting signatures have identical confidence levels; defer to the previously vetted signature\n                } else {\n                    debug!(\n                        \"Conflicting signatures at offset {:#X} with the same confidence; first come, first served\",\n                        this_signature.offset\n                    );\n                    file_map.remove(i);\n                    index_adjustment += 1;\n                    continue;\n                }\n\n            // Else, if the offsets don't conflict, make sure this signature doesn't fall inside a previously identified signature's data\n            } else if this_signature.offset < next_valid_offset {\n                debug!(\n                    \"Signature {} at offset {:#X} contains conflicting data; ignoring\",\n                    this_signature.name, this_signature.offset\n                );\n                file_map.remove(i);\n                index_adjustment += 1;\n                continue;\n            }\n\n            // If we've made it this far, make sure this signature's data doesn't extend beyond EOF and that the file data doesn't wrap around\n            if this_signature.size > remaining_available_size\n                || ((this_signature.offset + this_signature.size) as isize) < 0\n            {\n                debug!(\n                    \"Signature {} at offset {:#X} claims its size extends beyond EOF; ignoring\",\n                    this_signature.name, this_signature.offset\n                );\n                file_map.remove(i);\n                index_adjustment += 1;\n                continue;\n            }\n\n            // This signature looks OK, update the next_valid_offset to be the end of this signature's data, only if we're fairly confident in the signature\n            if this_signature.confidence >= signatures::common::CONFIDENCE_MEDIUM {\n                next_valid_offset = this_signature.offset + this_signature.size;\n            }\n        }\n\n        /*\n         * Ideally, all signatures would report their size; some file formats do not specify a size, and the only\n         * way to determine the size is to extract the file format (compressed data, for example).\n         * For signatures with a reported size of 0, update their size to be the start of the next signature, or EOF.\n         * This makes the assumption that there are no false positives or false negatives.\n         *\n         * False negatives (i.e., there is some other file format or data between this signature and the next that\n         * was not correctly identified) is less problematic, as this will overestimate the size of this signature,\n         * but most extraction utilities don't care about this extra trailing data being included.\n         *\n         * False positives (i.e., some data inside of this signature is identified as some other file type) can cause\n         * this signature's file data to become truncated, which will inevitably result in a failed, or partial, extraction.\n         *\n         * Thus, signatures must be very good at validating magic matches and eliminating false positives.\n         */\n        for i in 0..file_map.len() {\n            if file_map[i].size == 0 {\n                // Index of the next file map entry, if any\n                let next_index = i + 1;\n\n                // By default, assume this signature goes to EOF\n                let mut next_offset: usize = file_data.len();\n\n                // If there are more entries in the file map\n                if next_index < file_map.len() {\n                    // Look through all remaining file map entries for one with medium to high confidence\n                    for file_map_entry in file_map.iter().skip(next_index) {\n                        if file_map_entry.confidence >= signatures::common::CONFIDENCE_MEDIUM {\n                            // If a signature of at least medium confidence is found, assume that *this* signature ends there\n                            next_offset = file_map_entry.offset;\n                            break;\n                        }\n                    }\n                }\n\n                file_map[i].size = next_offset - file_map[i].offset;\n                warn!(\n                    \"Signature {}:{:#X} size is unknown; assuming size of {:#X} bytes\",\n                    file_map[i].name, file_map[i].offset, file_map[i].size\n                );\n            } else {\n                debug!(\n                    \"Signature {}:{:#X} has a reported size of {:#X} bytes\",\n                    file_map[i].name, file_map[i].offset, file_map[i].size\n                );\n            }\n        }\n\n        debug!(\"Found {} valid signatures\", file_map.len());\n\n        file_map\n    }\n\n    /// Extract all extractable signatures found in a file.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_529_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {\n    /// use binwalk::Binwalk;\n    ///\n    /// let target_path = std::path::Path::new(\"tests\")\n    ///     .join(\"inputs\")\n    ///     .join(\"gzip.bin\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let extraction_directory = std::path::Path::new(\"tests\")\n    ///     .join(\"extractions\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// let binwalker = Binwalk::configure(Some(target_path),\n    ///                                    Some(extraction_directory.clone()),\n    ///                                    None,\n    ///                                    None,\n    ///                                    None,\n    ///                                    false)?;\n    ///\n    /// let file_data = std::fs::read(&binwalker.base_target_file).expect(\"Unable to read file\");\n    ///\n    /// let scan_results = binwalker.scan(&file_data);\n    /// let extraction_results = binwalker.extract(&file_data, &binwalker.base_target_file, &scan_results);\n    ///\n    /// assert_eq!(scan_results.len(), 1);\n    /// assert_eq!(extraction_results.len(),  1);\n    /// assert_eq!(std::path::Path::new(&extraction_directory)\n    ///     .join(\"gzip.bin.extracted\")\n    ///     .join(\"0\")\n    ///     .join(\"decompressed.bin\")\n    ///     .exists(), true);\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// # Ok(binwalker)\n    /// # } _doctest_main_src_binwalk_rs_529_0(); }\n    /// ```\n    pub fn extract(\n        &self,\n        file_data: &[u8],\n        file_name: impl Into<String>,\n        file_map: &Vec<signatures::common::SignatureResult>,\n    ) -> HashMap<String, extractors::common::ExtractionResult> {\n        let file_path = file_name.into();\n        let mut extraction_results: HashMap<String, extractors::common::ExtractionResult> =\n            HashMap::new();\n\n        // Spawn extractors for each extractable signature\n        for signature in file_map {\n            // Signatures may opt to not perform extraction; honor this request\n            if signature.extraction_declined {\n                continue;\n            }\n\n            // Get the extractor for this signature\n            let extractor = self.extractor_lookup_table[&signature.name].clone();\n\n            match &extractor {\n                None => continue,\n                Some(_) => {\n                    // Run an extraction for this signature\n                    let mut extraction_result =\n                        extractors::common::execute(file_data, &file_path, signature, &extractor);\n\n                    if !extraction_result.success {\n                        debug!(\n                            \"Extraction failed for {} (ID: {}) {:#X} - {:#X}\",\n                            signature.name, signature.id, signature.offset, signature.size\n                        );\n\n                        // Calculate all available data from the start of this signature to EOF\n                        let available_data = file_data.len() - signature.offset;\n\n                        /*\n                         * If extraction failed, it could be due to truncated data (signature matching is not perfect ya know!)\n                         * In that case, make one more attempt, this time provide the extractor all the data possible.\n                         */\n                        if signature.size < available_data {\n                            // Create a duplicate signature, but set its reported size to the length of all available data\n                            let mut new_signature = signature.clone();\n                            new_signature.size = available_data;\n\n                            debug!(\n                                \"Trying extraction for {} (ID: {}) again, this time from {:#X} - {:#X}\",\n                                new_signature.name,\n                                new_signature.id,\n                                new_signature.offset,\n                                new_signature.size\n                            );\n\n                            // Re-run the extraction\n                            extraction_result = extractors::common::execute(\n                                file_data,\n                                &file_path,\n                                &new_signature,\n                                &extractor,\n                            );\n                        }\n                    }\n\n                    // Update the HashMap with the result of this extraction attempt\n                    extraction_results.insert(signature.id.clone(), extraction_result);\n                }\n            }\n        }\n\n        extraction_results\n    }\n\n    /// Analyze a data buffer and optionally extract the file contents.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_672_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {\n    /// use binwalk::{Binwalk, common};\n    ///\n    /// let target_path = std::path::Path::new(\"tests\")\n    ///     .join(\"inputs\")\n    ///     .join(\"gzip.bin\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let extraction_directory = std::path::Path::new(\"tests\")\n    ///     .join(\"extractions\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_data = common::read_file(&target_path).expect(\"Failed to read file data\");\n    ///\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// let binwalker = Binwalk::configure(Some(target_path),\n    ///                                    Some(extraction_directory.clone()),\n    ///                                    None,\n    ///                                    None,\n    ///                                    None,\n    ///                                    false)?;\n    ///\n    /// let analysis_results = binwalker.analyze_buf(&file_data, &binwalker.base_target_file, true);\n    ///\n    /// assert_eq!(analysis_results.file_map.len(), 1);\n    /// assert_eq!(analysis_results.extractions.len(),  1);\n    /// assert_eq!(std::path::Path::new(&extraction_directory)\n    ///     .join(\"gzip.bin.extracted\")\n    ///     .join(\"0\")\n    ///     .join(\"decompressed.bin\")\n    ///     .exists(), true);\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// # Ok(binwalker)\n    /// # } _doctest_main_src_binwalk_rs_672_0(); }\n    /// ```\n    pub fn analyze_buf(\n        &self,\n        file_data: &[u8],\n        target_file: impl Into<String>,\n        do_extraction: bool,\n    ) -> AnalysisResults {\n        let file_path = target_file.into();\n\n        // Return value\n        let mut results: AnalysisResults = AnalysisResults {\n            file_path: file_path.clone(),\n            ..Default::default()\n        };\n\n        // Scan file data for signatures\n        debug!(\"Analysis start: {file_path}\");\n        results.file_map = self.scan(file_data);\n\n        // Only extract if told to, and if there were some signatures found in this file\n        if do_extraction && !results.file_map.is_empty() {\n            // Extract everything we can\n            debug!(\n                \"Submitting {} signature results to extractor\",\n                results.file_map.len()\n            );\n            results.extractions = self.extract(file_data, &file_path, &results.file_map);\n        }\n\n        debug!(\"Analysis end: {file_path}\");\n\n        results\n    }\n\n    /// Analyze a file on disk and optionally extract its contents.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_binwalk_rs_745_0() -> Result<binwalk::Binwalk, binwalk::BinwalkError> {\n    /// use binwalk::Binwalk;\n    ///\n    /// let target_path = std::path::Path::new(\"tests\")\n    ///     .join(\"inputs\")\n    ///     .join(\"gzip.bin\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let extraction_directory = std::path::Path::new(\"tests\")\n    ///     .join(\"extractions\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// let binwalker = Binwalk::configure(Some(target_path),\n    ///                                    Some(extraction_directory.clone()),\n    ///                                    None,\n    ///                                    None,\n    ///                                    None,\n    ///                                    false)?;\n    ///\n    /// let analysis_results = binwalker.analyze(&binwalker.base_target_file, true);\n    ///\n    /// assert_eq!(analysis_results.file_map.len(), 1);\n    /// assert_eq!(analysis_results.extractions.len(),  1);\n    /// assert_eq!(std::path::Path::new(&extraction_directory)\n    ///     .join(\"gzip.bin.extracted\")\n    ///     .join(\"0\")\n    ///     .join(\"decompressed.bin\")\n    ///     .exists(), true);\n    /// # std::fs::remove_dir_all(&extraction_directory);\n    /// # Ok(binwalker)\n    /// # } _doctest_main_src_binwalk_rs_745_0(); }\n    /// ```\n    #[allow(dead_code)]\n    pub fn analyze(&self, target_file: impl Into<String>, do_extraction: bool) -> AnalysisResults {\n        let file_path = target_file.into();\n\n        let file_data = match read_file(&file_path) {\n            Err(_) => {\n                error!(\"Failed to read data from {file_path}\");\n                b\"\".to_vec()\n            }\n            Ok(data) => data,\n        };\n\n        self.analyze_buf(&file_data, &file_path, do_extraction)\n    }\n}\n\n/// Initializes the extraction output directory\nfn init_extraction_directory(\n    target_file: &str,\n    extraction_directory: &str,\n) -> Result<String, std::io::Error> {\n    // Create the output directory, equivalent of mkdir -p\n    match fs::create_dir_all(extraction_directory) {\n        Ok(_) => {\n            debug!(\"Created base output directory: '{extraction_directory}'\");\n        }\n        Err(e) => {\n            error!(\"Failed to create base output directory '{extraction_directory}': {e}\");\n            return Err(e);\n        }\n    }\n\n    // Create a Path for the target file\n    let target_path = path::Path::new(&target_file);\n\n    // Build a symlink path to the target file in the extraction directory\n    let link_target_path_str = format!(\n        \"{}{}{}\",\n        extraction_directory,\n        path::MAIN_SEPARATOR,\n        target_path.file_name().unwrap().to_str().unwrap()\n    );\n\n    // Create a path for the symlink target path\n    let link_path = path::Path::new(&link_target_path_str);\n\n    if link_path.exists() {\n        return Ok(link_target_path_str);\n    }\n\n    debug!(\n        \"Creating symlink from {} -> {}\",\n        link_path.display(),\n        target_path.display()\n    );\n\n    // Create a symlink from inside the extraction directory to the specified target file\n    #[cfg(unix)]\n    {\n        match unix::fs::symlink(target_path, link_path) {\n            Ok(_) => Ok(link_target_path_str),\n            Err(e) => {\n                error!(\n                    \"Failed to create symlink {} -> {}: {}\",\n                    link_path.display(),\n                    target_path.display(),\n                    e\n                );\n                Err(e)\n            }\n        }\n    }\n    #[cfg(windows)]\n    {\n        match std::fs::hard_link(target_path, link_path) {\n            Ok(_) => {\n                return Ok(link_target_path_str);\n            }\n            Err(e) => {\n                error!(\n                    \"Failed to create hardlink {} -> {}: {}\",\n                    link_path.display(),\n                    target_path.display(),\n                    e\n                );\n                return Err(e);\n            }\n        }\n    }\n}\n\n/// Returns true if the signature should be included for file analysis, else returns false.\nfn include_signature(\n    signature: &signatures::common::Signature,\n    include: &Option<Vec<String>>,\n    exclude: &Option<Vec<String>>,\n) -> bool {\n    if let Some(include_signatures) = include {\n        for include_str in include_signatures {\n            if signature.name.to_lowercase() == include_str.to_lowercase() {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    if let Some(exclude_signatures) = exclude {\n        for exclude_str in exclude_signatures {\n            if signature.name.to_lowercase() == exclude_str.to_lowercase() {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    true\n}\n\n/// Some SignatureResult fields need to be auto-populated.\nfn signature_result_auto_populate(\n    signature_result: &mut signatures::common::SignatureResult,\n    signature: &signatures::common::Signature,\n) {\n    signature_result.id = Uuid::new_v4().to_string();\n    signature_result.name = signature.name.clone();\n    signature_result.always_display = signature.always_display;\n}\n"
  },
  {
    "path": "src/cliparser.rs",
    "content": "use clap::{CommandFactory, Parser};\n\n#[derive(Debug, Parser)]\n#[command(author, version, about, long_about = None)]\npub struct CliArgs {\n    /// List supported signatures and extractors\n    #[arg(short = 'L', long)]\n    pub list: bool,\n\n    /// Read data from standard input\n    #[arg(short, long)]\n    pub stdin: bool,\n\n    /// Supress normal stdout output\n    #[arg(short, long)]\n    pub quiet: bool,\n\n    /// During recursive extraction display *all* results\n    #[arg(short, long)]\n    pub verbose: bool,\n\n    /// Automatically extract known file types\n    #[arg(short, long)]\n    pub extract: bool,\n\n    /// Carve both known and unknown file contents to disk\n    #[arg(short, long)]\n    pub carve: bool,\n\n    /// Recursively scan extracted files\n    #[arg(short = 'M', long)]\n    pub matryoshka: bool,\n\n    /// Search for all signatures at all offsets\n    #[arg(short = 'a', long)]\n    pub search_all: bool,\n\n    /// Generate an entropy graph with Plotly\n    #[arg(short = 'E', long, conflicts_with = \"extract\")]\n    pub entropy: bool,\n\n    /// Save entropy graph as a PNG file\n    #[arg(short, long)]\n    pub png: Option<String>,\n\n    /// Log JSON results to a file ('-' for stdout)\n    #[arg(short, long)]\n    pub log: Option<String>,\n\n    /// Manually specify the number of threads to use\n    #[arg(short, long)]\n    pub threads: Option<usize>,\n\n    /// Do no scan for these signatures\n    #[arg(short = 'x', long, value_delimiter = ',', num_args = 1..)]\n    pub exclude: Option<Vec<String>>,\n\n    /// Only scan for these signatures\n    #[arg(short = 'y', long, value_delimiter = ',', num_args = 1.., conflicts_with = \"exclude\")]\n    pub include: Option<Vec<String>>,\n\n    /// Extract files/folders to a custom directory\n    #[arg(short, long, default_value = \"extractions\")]\n    pub directory: String,\n\n    /// Path to the file to analyze\n    pub file_name: Option<String>,\n}\n\npub fn parse() -> CliArgs {\n    let args = CliArgs::parse();\n\n    if std::env::args().len() == 1 {\n        CliArgs::command()\n            .print_help()\n            .expect(\"Failed to print help output\");\n        std::process::exit(0);\n    }\n\n    args\n}\n"
  },
  {
    "path": "src/common.rs",
    "content": "//! Common Functions\nuse chrono::prelude::DateTime;\nuse log::{debug, error};\nuse std::fs::File;\nuse std::io::Read;\n\n/// Read a data into memory, either from disk or from stdin, and return its contents.\n///\n/// ## Example\n///\n/// ```\n/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_11_0() -> Result<(), Box<dyn std::error::Error>> {\n/// use binwalk::common::read_input;\n///\n/// let file_data = read_input(\"/etc/passwd\", false)?;\n/// assert!(file_data.len() > 0);\n/// # Ok(())\n/// # } _doctest_main_src_common_rs_11_0(); }\n/// ```\npub fn read_input(file: impl Into<String>, stdin: bool) -> Result<Vec<u8>, std::io::Error> {\n    if stdin { read_stdin() } else { read_file(file) }\n}\n\n/// Read data from standard input and return its contents.\npub fn read_stdin() -> Result<Vec<u8>, std::io::Error> {\n    let mut stdin_data = Vec::new();\n\n    match std::io::stdin().read_to_end(&mut stdin_data) {\n        Err(e) => {\n            error!(\"Failed to read data from stdin: {e}\");\n            Err(e)\n        }\n        Ok(nbytes) => {\n            debug!(\"Loaded {nbytes} bytes from stdin\");\n            Ok(stdin_data)\n        }\n    }\n}\n\n/// Read a file data into memory and return its contents.\n///\n/// ## Example\n///\n/// ```\n/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_common_rs_48_0() -> Result<(), Box<dyn std::error::Error>> {\n/// use binwalk::common::read_file;\n///\n/// let file_data = read_file(\"/etc/passwd\")?;\n/// assert!(file_data.len() > 0);\n/// # Ok(())\n/// # } _doctest_main_src_common_rs_48_0(); }\n/// ```\npub fn read_file(file: impl Into<String>) -> Result<Vec<u8>, std::io::Error> {\n    let mut file_data = Vec::new();\n    let file_path = file.into();\n\n    match File::open(&file_path) {\n        Err(e) => {\n            error!(\"Failed to open file {file_path}: {e}\");\n            Err(e)\n        }\n        Ok(mut fp) => match fp.read_to_end(&mut file_data) {\n            Err(e) => {\n                error!(\"Failed to read file {file_path} into memory: {e}\");\n                Err(e)\n            }\n            Ok(file_size) => {\n                debug!(\"Loaded {file_size} bytes from {file_path}\");\n                Ok(file_data)\n            }\n        },\n    }\n}\n\n/// Calculates the CRC32 checksum of the given data.\n///\n/// ## Notes\n///\n/// Uses initial CRC value of 0.\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::crc32;\n///\n/// let my_data: &[u8] = b\"ABCD\";\n///\n/// let my_data_crc = crc32(my_data);\n///\n/// assert_eq!(my_data_crc, 0xDB1720A5);\n/// ```\npub fn crc32(data: &[u8]) -> u32 {\n    crc32_v2::crc32(0, data)\n}\n\n/// Converts an epoch time to a formatted time string.\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::epoch_to_string;\n///\n/// let timestamp = epoch_to_string(0);\n///\n/// assert_eq!(timestamp, \"1970-01-01 00:00:00\");\n/// ```\npub fn epoch_to_string(epoch_timestamp: u32) -> String {\n    let date_time = DateTime::from_timestamp(epoch_timestamp.into(), 0);\n    match date_time {\n        Some(dt) => dt.format(\"%Y-%m-%d %H:%M:%S\").to_string(),\n        None => \"\".to_string(),\n    }\n}\n\n/// Get a C-style NULL-terminated string from the provided list of u8 bytes.\n/// Return value does not include the terminating NULL byte.\nfn get_cstring_bytes(raw_data: &[u8]) -> Vec<u8> {\n    let mut cstring: Vec<u8> = vec![];\n\n    for raw_byte in raw_data {\n        if *raw_byte == 0 {\n            break;\n        } else {\n            cstring.push(*raw_byte);\n        }\n    }\n\n    cstring\n}\n\n/// Get a C-style NULL-terminated string from the provided array of u8 bytes.\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::get_cstring;\n///\n/// let raw_data: &[u8] = b\"this_is_a_c_string\\x00\";\n///\n/// let string = get_cstring(raw_data);\n///\n/// assert_eq!(string, \"this_is_a_c_string\");\n/// ```\npub fn get_cstring(raw_data: &[u8]) -> String {\n    let raw_string = get_cstring_bytes(raw_data);\n\n    let string: String = match String::from_utf8(raw_string) {\n        Err(_) => \"\".to_string(),\n        Ok(s) => s.clone(),\n    };\n\n    string\n}\n\n/// Returns true if the provided byte is an ASCII number\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::is_ascii_number;\n///\n/// assert!(is_ascii_number(0x31));\n/// assert!(!is_ascii_number(0xFE));\n/// ```\npub fn is_ascii_number(b: u8) -> bool {\n    const ZERO: u8 = 48;\n    const NINE: u8 = 57;\n\n    (ZERO..=NINE).contains(&b)\n}\n\n/// Returns true if the provided byte is a printable ASCII character\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::is_printable_ascii;\n///\n/// assert!(is_printable_ascii(0x41));\n/// assert!(!is_printable_ascii(0xFE));\n/// ```\npub fn is_printable_ascii(b: u8) -> bool {\n    const ASCII_MIN: u8 = 0x0A;\n    const ASCII_MAX: u8 = 0x7E;\n\n    (ASCII_MIN..=ASCII_MAX).contains(&b)\n}\n\n/// Validates data offsets to prevent out-of-bounds access and infinite loops while parsing file formats.\n///\n/// ## Notes\n///\n/// - `next_offset` must be within the bounds of `available_data`\n/// - `previous_offset` must be less than `next_offset`, or `None`\n///\n/// ## Example\n///\n/// ```\n/// use binwalk::common::is_offset_safe;\n///\n/// let my_data: &[u8] = b\"ABCD\";\n/// let available_data = my_data.len();\n///\n/// assert!(is_offset_safe(available_data, 0, None));\n/// assert!(!is_offset_safe(available_data, 4, None));\n/// assert!(is_offset_safe(available_data, 2, Some(1)));\n/// assert!(!is_offset_safe(available_data, 2, Some(2)));\n/// assert!(!is_offset_safe(available_data, 1, Some(2)));\n/// ```\npub fn is_offset_safe(\n    available_data: usize,\n    next_offset: usize,\n    last_offset: Option<usize>,\n) -> bool {\n    // If a previous file offset was specified, ensure that it is less than the next file offset\n    if let Some(previous_offset) = last_offset {\n        if previous_offset >= next_offset {\n            return false;\n        }\n    }\n\n    // Ensure that the next file offset is within the bounds of available file data\n    if next_offset >= available_data {\n        return false;\n    }\n\n    true\n}\n"
  },
  {
    "path": "src/display.rs",
    "content": "use crate::binwalk::AnalysisResults;\nuse crate::extractors;\nuse crate::signatures;\nuse colored::ColoredString;\nuse colored::Colorize;\nuse log::error;\nuse std::collections::HashMap;\nuse std::io;\nuse std::io::Write;\nuse std::time;\n\nconst DELIM_CHARACTER: &str = \"-\";\nconst DEFAULT_TERMINAL_WIDTH: u16 = 200;\n\nconst COLUMN1_WIDTH: usize = 35;\nconst COLUMN2_WIDTH: usize = 35;\n\nfn terminal_width() -> usize {\n    let terminal_width: u16 = match termsize::get() {\n        Some(ts) => ts.cols,\n        None => DEFAULT_TERMINAL_WIDTH,\n    };\n\n    terminal_width as usize\n}\n\nfn line_delimiter() -> String {\n    let mut delim: String = \"\".to_string();\n\n    for _i in 0..terminal_width() {\n        delim += DELIM_CHARACTER;\n    }\n\n    delim\n}\n\nfn center_text(text: &str) -> String {\n    let mut padding_width: i32;\n    let mut centered_string: String = \"\".to_string();\n\n    match ((terminal_width() / 2) - (text.len() / 2)).try_into() {\n        Err(_e) => padding_width = 0,\n        Ok(value) => padding_width = value,\n    }\n\n    if padding_width < 0 {\n        padding_width = 0;\n    }\n\n    for _i in 0..padding_width {\n        centered_string += \" \";\n    }\n\n    centered_string += text;\n\n    centered_string\n}\n\nfn pad_to_length(text: &str, len: usize) -> String {\n    let mut pad_size: i32;\n    let mut padded_string = String::from(text);\n\n    match (len - text.len()).try_into() {\n        Err(_e) => pad_size = 0,\n        Ok(value) => pad_size = value,\n    }\n\n    if pad_size < 0 {\n        pad_size = 0;\n    }\n\n    for _i in 0..pad_size {\n        padded_string += \" \";\n    }\n\n    padded_string\n}\n\nfn line_wrap(text: &str, prefix_size: usize) -> String {\n    let mut this_line = \"\".to_string();\n    let mut formatted_string = \"\".to_string();\n    let max_line_size: usize = terminal_width() - prefix_size;\n\n    for word in text.split_whitespace() {\n        if (this_line.len() + word.len()) < max_line_size {\n            this_line = this_line + word + \" \";\n        } else {\n            formatted_string = formatted_string + &this_line + \"\\n\";\n            for _i in 0..prefix_size {\n                formatted_string += \" \";\n            }\n            this_line = word.to_string() + \" \";\n        }\n    }\n\n    formatted_string = formatted_string + &this_line;\n\n    formatted_string.trim().to_string()\n}\n\nfn print_column_headers(col1: &str, col2: &str, col3: &str) {\n    let header_string = format!(\n        \"{}{}{}\",\n        pad_to_length(col1, COLUMN1_WIDTH),\n        pad_to_length(col2, COLUMN2_WIDTH),\n        col3\n    );\n\n    println!(\"{}\", header_string.bold().bright_blue());\n}\n\nfn print_delimiter() {\n    println!(\"{}\", line_delimiter().bold().bright_blue());\n}\n\nfn print_header(title_text: &str) {\n    println!();\n    println!(\"{}\", center_text(title_text).bold().magenta());\n    print_delimiter();\n    print_column_headers(\"DECIMAL\", \"HEXADECIMAL\", \"DESCRIPTION\");\n    print_delimiter();\n}\n\nfn print_footer() {\n    print_delimiter();\n    println!();\n}\n\nfn print_signature(signature: &signatures::common::SignatureResult) {\n    let decimal_string = format!(\"{}\", signature.offset);\n    let hexadecimal_string = format!(\"{:#X}\", signature.offset);\n    let display_string = format!(\n        \"{}{}{}\",\n        pad_to_length(&decimal_string, COLUMN1_WIDTH),\n        pad_to_length(&hexadecimal_string, COLUMN2_WIDTH),\n        line_wrap(&signature.description, COLUMN1_WIDTH + COLUMN2_WIDTH)\n    );\n\n    if signature.confidence >= signatures::common::CONFIDENCE_HIGH {\n        println!(\"{}\", display_string.green());\n    } else if signature.confidence >= signatures::common::CONFIDENCE_MEDIUM {\n        println!(\"{}\", display_string.yellow());\n    } else {\n        println!(\"{}\", display_string.red());\n    }\n}\n\nfn print_signatures(signatures: &Vec<signatures::common::SignatureResult>) {\n    for signature in signatures {\n        print_signature(signature);\n    }\n}\n\nfn print_extraction(\n    signature: &signatures::common::SignatureResult,\n    extraction: Option<&extractors::common::ExtractionResult>,\n) {\n    let extraction_message: ColoredString;\n\n    match extraction {\n        None => {\n            extraction_message = format!(\n                \"[#] Extraction of {} data at offset {:#X} declined\",\n                signature.name, signature.offset\n            )\n            .bold()\n            .yellow();\n        }\n        Some(extraction_result) => {\n            if extraction_result.success {\n                extraction_message = format!(\n                    \"[+] Extraction of {} data at offset {:#X} completed successfully\",\n                    signature.name, signature.offset\n                )\n                .bold()\n                .green();\n            } else {\n                extraction_message = format!(\n                    \"[-] Extraction of {} data at offset {:#X} failed!\",\n                    signature.name, signature.offset\n                )\n                .bold()\n                .red();\n            }\n        }\n    }\n\n    println!(\"{extraction_message}\");\n}\n\nfn print_extractions(\n    signatures: &Vec<signatures::common::SignatureResult>,\n    extraction_results: &HashMap<String, extractors::common::ExtractionResult>,\n) {\n    let mut delimiter_printed: bool = false;\n\n    for signature in signatures {\n        let mut printable_extraction: bool = false;\n        let mut extraction_result: Option<&extractors::common::ExtractionResult> = None;\n\n        // Only print extraction results if an extraction was attempted or explicitly declined\n        if signature.extraction_declined {\n            printable_extraction = true\n        } else if extraction_results.contains_key(&signature.id) {\n            printable_extraction = true;\n            extraction_result = Some(&extraction_results[&signature.id]);\n        }\n\n        if printable_extraction {\n            // Only print the delimiter line once\n            if !delimiter_printed {\n                print_delimiter();\n                delimiter_printed = true;\n            }\n            print_extraction(signature, extraction_result);\n        }\n    }\n}\n\npub fn print_analysis_results(quiet: bool, extraction_attempted: bool, results: &AnalysisResults) {\n    if quiet {\n        return;\n    }\n\n    // Print signature results\n    print_header(&results.file_path);\n    print_signatures(&results.file_map);\n\n    // If extraction was attempted, print extraction results\n    if extraction_attempted {\n        print_extractions(&results.file_map, &results.extractions);\n    }\n\n    // Print the footer text\n    print_footer();\n}\n\n// Used by print_signature_list\n#[derive(Debug, Default, Clone)]\nstruct SignatureInfo {\n    name: String,\n    is_short: bool,\n    has_extractor: bool,\n    extractor: String,\n    description: String,\n}\n\npub fn print_signature_list(quiet: bool, signatures: &Vec<signatures::common::Signature>) {\n    let mut extractor_count: usize = 0;\n    let mut signature_count: usize = 0;\n    let mut sorted_descriptions: Vec<String> = vec![];\n    let mut signature_lookup: HashMap<String, SignatureInfo> = HashMap::new();\n\n    if quiet {\n        return;\n    }\n\n    // Print column headers\n    print_delimiter();\n    print_column_headers(\n        \"Signature Description\",\n        \"Signature Name\",\n        \"Extraction Utility\",\n    );\n    print_delimiter();\n\n    // Loop through all signatures\n    for signature in signatures {\n        // Convenience struct for storing some basic info about each signature\n        let mut signature_info = SignatureInfo {\n            ..Default::default()\n        };\n\n        // Keep track of signature name, description, and if the signature is a \"short\" signature\n        signature_info.name = signature.name.clone();\n        signature_info.is_short = signature.short;\n        signature_info.description = signature.description.clone();\n\n        // Keep track of which signatures have associated extractors, and if so, what type of extractor\n        match &signature.extractor {\n            None => {\n                signature_info.has_extractor = false;\n                signature_info.extractor = \"None\".to_string();\n            }\n            Some(extractor) => {\n                signature_info.has_extractor = true;\n\n                match &extractor.utility {\n                    extractors::common::ExtractorType::External(command) => {\n                        signature_info.extractor = command.to_string();\n                    }\n                    extractors::common::ExtractorType::Internal(_) => {\n                        signature_info.extractor = \"Built-in\".to_string();\n                    }\n                    extractors::common::ExtractorType::None => error!(\n                        \"An invalid extractor type exists for the '{}' signature\",\n                        signature.description\n                    ),\n                }\n            }\n        }\n\n        // Increment signature count\n        signature_count += 1;\n\n        // If there is an extractor for this signature, increment extractor count\n        if signature_info.has_extractor {\n            extractor_count += 1;\n        }\n\n        // Keep signature descriptions in a separate list, which wil be sorted alphabetically for display\n        sorted_descriptions.push(signature_info.description.clone());\n\n        // Lookup table associating signature descriptions with their SignatureInfo struct\n        signature_lookup.insert(signature.description.clone(), signature_info.clone());\n    }\n\n    // Sort signature descriptions alphabetically\n    sorted_descriptions.sort_by_key(|description| description.to_lowercase());\n\n    // Print signatures, sorted alphabetically by description\n    for description in sorted_descriptions {\n        let siginfo = &signature_lookup[&description];\n\n        let display_line = format!(\n            \"{}{}{}\",\n            pad_to_length(&description, COLUMN1_WIDTH),\n            pad_to_length(&siginfo.name, COLUMN2_WIDTH),\n            siginfo.extractor\n        );\n\n        if siginfo.is_short {\n            println!(\"{}\", display_line.yellow());\n        } else {\n            println!(\"{}\", display_line.green());\n        }\n    }\n\n    print_delimiter();\n    println!();\n    println!(\"Total signatures: {signature_count}\");\n    println!(\"Extractable signatures: {extractor_count}\");\n}\n\npub fn print_stats(\n    quiet: bool,\n    run_time: time::Instant,\n    file_count: usize,\n    signature_count: usize,\n    pattern_count: usize,\n) {\n    const MS_IN_A_SECOND: f64 = 1000.0;\n    const SECONDS_IN_A_MINUTE: f64 = 60.0;\n    const MINUTES_IN_AN_HOUR: f64 = 60.0;\n\n    let mut file_plural = \"\";\n    let mut units = \"milliseconds\";\n    let mut display_time: f64 = run_time.elapsed().as_millis() as f64;\n\n    if quiet {\n        return;\n    }\n\n    // Format the output time in a more human-readable manner\n    if display_time >= MS_IN_A_SECOND {\n        display_time /= MS_IN_A_SECOND;\n        units = \"seconds\";\n\n        if display_time >= SECONDS_IN_A_MINUTE {\n            display_time /= SECONDS_IN_A_MINUTE;\n            units = \"minutes\";\n\n            if display_time >= MINUTES_IN_AN_HOUR {\n                display_time /= MINUTES_IN_AN_HOUR;\n                units = \"hours\";\n            }\n        }\n    }\n\n    if file_count != 1 {\n        file_plural = \"s\";\n    }\n\n    println!(\n        \"Analyzed {file_count} file{file_plural} for {signature_count} file signatures ({pattern_count} magic patterns) in {display_time:.1} {units}\"\n    );\n}\n\npub fn print_plain(quiet: bool, msg: &str) {\n    if !quiet {\n        print!(\"{msg}\");\n        let _ = io::stdout().flush();\n    }\n}\n\npub fn println_plain(quiet: bool, msg: &str) {\n    if !quiet {\n        println!(\"{msg}\");\n    }\n}\n"
  },
  {
    "path": "src/entropy.rs",
    "content": "use crate::common::read_input;\nuse entropy::shannon_entropy;\nuse plotly::layout::{Axis, Layout};\nuse plotly::{ImageFormat, Plot, Scatter};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone)]\npub struct EntropyError;\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct BlockEntropy {\n    pub end: usize,\n    pub start: usize,\n    pub entropy: f32,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct FileEntropy {\n    pub file: String,\n    pub blocks: Vec<BlockEntropy>,\n}\n\n/// Splits the supplied data up into blocks and calculates the entropy of each block.\nfn blocks(data: &[u8]) -> Vec<BlockEntropy> {\n    const BLOCK_COUNT: usize = 2048;\n\n    let mut offset: usize = 0;\n\n    let block_size = if data.len() < BLOCK_COUNT {\n        data.len()\n    } else {\n        data.len() / BLOCK_COUNT\n    };\n\n    let mut chunker = data.chunks(block_size);\n    let mut entropy_blocks: Vec<BlockEntropy> = vec![];\n\n    loop {\n        match chunker.next() {\n            None => break,\n            Some(block_data) => {\n                let mut block = BlockEntropy {\n                    ..Default::default()\n                };\n\n                block.start = offset;\n                block.entropy = shannon_entropy(block_data);\n                block.end = block.start + block_data.len();\n\n                offset = block.end;\n                entropy_blocks.push(block);\n            }\n        }\n    }\n\n    entropy_blocks\n}\n\npub fn plot(\n    file_path: impl Into<String>,\n    stdin: bool,\n    out_file: Option<String>,\n) -> Result<FileEntropy, EntropyError> {\n    let mut x: Vec<usize> = Vec::new();\n    let mut y: Vec<f32> = Vec::new();\n    let target_file: String = file_path.into();\n    let mut file_entropy = FileEntropy {\n        file: target_file.clone(),\n        ..Default::default()\n    };\n\n    // Read in the target file data\n    if let Ok(file_data) = read_input(&target_file, stdin) {\n        // Calculate the entropy of each file block\n        file_entropy.blocks = blocks(&file_data);\n\n        for block in &file_entropy.blocks {\n            x.push(block.start);\n            x.push(block.end);\n            y.push(block.entropy);\n            y.push(block.entropy);\n        }\n\n        let mut plot = Plot::new();\n        let trace = Scatter::new(x, y);\n        let layout = Layout::new()\n            .title(\"Entropy Graph\")\n            .x_axis(Axis::new().title(\"File Offset\"))\n            .y_axis(Axis::new().title(\"Entropy\").range(vec![0, 8]));\n\n        plot.add_trace(trace);\n        plot.set_layout(layout);\n\n        match out_file {\n            None => plot.show(),\n            Some(out_file_name) => {\n                // TODO: Switch to plotly_static, which is the recommended way to do this\n                #[allow(deprecated)]\n                plot.write_image(&out_file_name, ImageFormat::PNG, 2048, 1024, 1.0);\n            }\n        }\n\n        return Ok(file_entropy);\n    }\n\n    Err(EntropyError)\n}\n"
  },
  {
    "path": "src/extractors/androidsparse.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::androidsparse;\n\n/// Defines the internal extractor function for extracting Android Sparse files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::androidsparse::android_sparse_extractor;\n///\n/// match android_sparse_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn android_sparse_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_android_sparse),\n        ..Default::default()\n    }\n}\n\n/// Android sparse internal extractor\npub fn extract_android_sparse(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"unsparsed.img\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the sparse file header\n    if let Ok(sparse_header) = androidsparse::parse_android_sparse_header(&file_data[offset..]) {\n        let available_data: usize = file_data.len();\n        let mut last_chunk_offset: Option<usize> = None;\n        let mut processed_chunk_count: usize = 0;\n        let mut next_chunk_offset: usize = offset + sparse_header.header_size;\n\n        while is_offset_safe(available_data, next_chunk_offset, last_chunk_offset) {\n            // Parse the next chunk's header\n            match androidsparse::parse_android_sparse_chunk_header(&file_data[next_chunk_offset..])\n            {\n                Err(_) => {\n                    break;\n                }\n\n                Ok(chunk_header) => {\n                    // If not a dry run, extract the data from the next chunk\n                    if output_directory.is_some() {\n                        let chroot = Chroot::new(output_directory);\n                        let chunk_data_start: usize = next_chunk_offset + chunk_header.header_size;\n                        let chunk_data_end: usize = chunk_data_start + chunk_header.data_size;\n\n                        if let Some(chunk_data) = file_data.get(chunk_data_start..chunk_data_end) {\n                            if !extract_chunk(\n                                &sparse_header,\n                                &chunk_header,\n                                chunk_data,\n                                OUTFILE_NAME,\n                                &chroot,\n                            ) {\n                                break;\n                            }\n                        } else {\n                            break;\n                        }\n                    }\n\n                    processed_chunk_count += 1;\n                    last_chunk_offset = Some(next_chunk_offset);\n                    next_chunk_offset += chunk_header.header_size + chunk_header.data_size;\n                }\n            }\n        }\n\n        // Make sure the number of processed chunks equals the number of chunks reported in the sparse flie header\n        if processed_chunk_count == sparse_header.chunk_count {\n            result.success = true;\n            result.size = Some(next_chunk_offset - offset);\n        }\n    }\n\n    result\n}\n\n// Extract a sparse file chunk to disk\nfn extract_chunk(\n    sparse_header: &androidsparse::AndroidSparseHeader,\n    chunk_header: &androidsparse::AndroidSparseChunkHeader,\n    chunk_data: &[u8],\n    outfile: &str,\n    chroot: &Chroot,\n) -> bool {\n    if chunk_header.is_raw {\n        // Raw chunks are just data chunks stored verbatim\n        if !chroot.append_to_file(outfile, chunk_data) {\n            return false;\n        }\n    } else if chunk_header.is_fill {\n        // Fill chunks are block_count blocks that contain a repeated sequence of data (typically 4-bytes repeated over and over again)\n        for _ in 0..chunk_header.block_count {\n            let mut i = 0;\n            let mut fill_block: Vec<u8> = vec![];\n\n            // Fill each block with the repeated data\n            while i < sparse_header.block_size {\n                fill_block.extend(chunk_data);\n                i += chunk_data.len();\n            }\n\n            // Append fill block to file\n            if !chroot.append_to_file(outfile, &fill_block) {\n                return false;\n            }\n        }\n    } else if chunk_header.is_dont_care {\n        let mut null_block: Vec<u8> = vec![];\n\n        // Build a block full of NULL bytes\n        while null_block.len() < sparse_header.block_size {\n            null_block.push(0);\n        }\n\n        // Write block_count NULL blocks to disk\n        for _ in 0..chunk_header.block_count {\n            if !chroot.append_to_file(outfile, &null_block) {\n                return false;\n            }\n        }\n    }\n\n    true\n}\n"
  },
  {
    "path": "src/extractors/arcadyan.rs",
    "content": "use crate::extractors::common::{ExtractionResult, Extractor, ExtractorType};\nuse crate::extractors::lzma::lzma_decompress;\n\n/// Defines the internal extractor for Arcadyn Obfuscated LZMA\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::arcadyan::obfuscated_lzma_extractor;\n///\n/// match obfuscated_lzma_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn obfuscated_lzma_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_obfuscated_lzma),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for Arcadyn Obfuscated LZMA\npub fn extract_obfuscated_lzma(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const LZMA_DATA_OFFSET: usize = 4;\n    const MIN_DATA_SIZE: usize = 0x100;\n    const MAX_DATA_SIZE: usize = 0x1B0000;\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n    let available_data: usize = file_data.len() - offset;\n\n    // Sanity check data size\n    if available_data <= MAX_DATA_SIZE && available_data > MIN_DATA_SIZE {\n        // De-obfuscate the LZMA data\n        let deobfuscated_data = arcadyan_deobfuscator(&file_data[offset..]);\n\n        // Do a decompression on the LZMA data (actual LZMA data starts 4 bytes into the deobfuscated data)\n        result = lzma_decompress(&deobfuscated_data, LZMA_DATA_OFFSET, output_directory);\n    }\n\n    result\n}\n\nfn arcadyan_deobfuscator(obfuscated_data: &[u8]) -> Vec<u8> {\n    const BLOCK_SIZE: usize = 32;\n\n    const P1_START: usize = 0;\n    const P1_END: usize = 4;\n\n    const BLOCK1_START: usize = P1_END;\n    const BLOCK1_END: usize = BLOCK1_START + BLOCK_SIZE;\n\n    const P2_START: usize = BLOCK1_END;\n    const P2_END: usize = 0x68;\n\n    const BLOCK2_START: usize = P2_END;\n    const BLOCK2_END: usize = BLOCK2_START + BLOCK_SIZE;\n\n    const P3_START: usize = BLOCK2_END;\n\n    let mut deobfuscated_data: Vec<u8> = vec![];\n\n    // Get the \"parts\" and \"blocks\" of the obfuscated header\n    let p1 = obfuscated_data[P1_START..P1_END].to_vec();\n    let b1 = obfuscated_data[BLOCK1_START..BLOCK1_END].to_vec();\n    let p2 = obfuscated_data[P2_START..P2_END].to_vec();\n    let b2 = obfuscated_data[BLOCK2_START..BLOCK2_END].to_vec();\n    let p3 = obfuscated_data[P3_START..].to_vec();\n\n    // Swap \"block1\" and \"block2\"\n    deobfuscated_data.extend(p1);\n    deobfuscated_data.extend(b2);\n    deobfuscated_data.extend(p2);\n    deobfuscated_data.extend(b1);\n    deobfuscated_data.extend(p3);\n\n    // Nibble swap each byte in what is now \"block1\"\n    for swapped_byte in deobfuscated_data\n        .iter_mut()\n        .take(BLOCK1_END)\n        .skip(BLOCK1_START)\n    {\n        *swapped_byte = ((*swapped_byte & 0x0F) << 4) + ((*swapped_byte & 0xF0) >> 4);\n    }\n\n    let mut i: usize = BLOCK1_START;\n\n    // Byte swap each byte in what is now \"block1\"\n    while i < BLOCK1_END {\n        let b1 = deobfuscated_data[i];\n        let b2 = deobfuscated_data[i + 1];\n        deobfuscated_data[i] = b2;\n        deobfuscated_data[i + 1] = b1;\n        i += 2;\n    }\n\n    deobfuscated_data\n}\n"
  },
  {
    "path": "src/extractors/autel.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::autel::parse_autel_header;\n\nconst BLOCK_SIZE: usize = 256;\n\n/// Defines the internal extractor function for deobfuscating Autel firmware\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::autel::autel_extractor;\n///\n/// match autel_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn autel_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(autel_deobfuscate),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for obfuscated Autel firmware\n/// https://gist.github.com/sector7-nl/3fc815cd2497817ad461bfbd393294cb\npub fn autel_deobfuscate(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTPUT_FILE_NAME: &str = \"autel.decoded\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse and validate the header\n    if let Ok(autel_header) = parse_autel_header(&file_data[offset..]) {\n        // Get the start and end offsets of the actual encoded data\n        let data_start = offset + autel_header.header_size;\n        let data_end = data_start + autel_header.data_size;\n\n        // Get the encoded data\n        if let Some(autel_data) = file_data.get(data_start..data_end) {\n            // Interate through each block of the encoded data\n            let mut block_iter = autel_data.chunks(BLOCK_SIZE);\n\n            loop {\n                match block_iter.next() {\n                    None => {\n                        // EOF\n                        result.size = Some(autel_header.data_size);\n                        result.success = true;\n                        break;\n                    }\n                    Some(block_bytes) => {\n                        // Decode the data block\n                        let decoded_block = decode_autel_block(block_bytes);\n\n                        // Write to file, if requested\n                        if output_directory.is_some() {\n                            let chroot = Chroot::new(output_directory);\n                            if !chroot.append_to_file(OUTPUT_FILE_NAME, &decoded_block) {\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n\n/// Block decoder for autel encoded firmware.\n/// block_data *must* be 256 bytes in size, or less.\nfn decode_autel_block(block_data: &[u8]) -> Vec<u8> {\n    // Lookup table for encoding/decoding bytes\n    let encoding_table: Vec<(usize, usize)> = vec![\n        (54, 147),\n        (96, 129),\n        (59, 193),\n        (191, 0),\n        (45, 130),\n        (96, 144),\n        (27, 129),\n        (152, 0),\n        (44, 180),\n        (118, 141),\n        (115, 129),\n        (210, 0),\n        (13, 164),\n        (27, 133),\n        (20, 192),\n        (139, 0),\n        (28, 166),\n        (17, 133),\n        (19, 193),\n        (224, 0),\n        (20, 161),\n        (145, 0),\n        (14, 193),\n        (12, 132),\n        (18, 161),\n        (17, 140),\n        (29, 192),\n        (246, 0),\n        (115, 178),\n        (28, 132),\n        (155, 0),\n        (12, 132),\n        (31, 165),\n        (20, 136),\n        (27, 193),\n        (142, 0),\n        (96, 164),\n        (18, 133),\n        (145, 0),\n        (23, 132),\n        (13, 165),\n        (13, 148),\n        (23, 193),\n        (19, 132),\n        (27, 178),\n        (83, 137),\n        (146, 0),\n        (145, 0),\n        (18, 166),\n        (96, 148),\n        (13, 193),\n        (159, 0),\n        (96, 166),\n        (20, 129),\n        (20, 193),\n        (27, 132),\n        (9, 160),\n        (96, 148),\n        (13, 192),\n        (159, 0),\n        (96, 180),\n        (142, 0),\n        (31, 193),\n        (155, 0),\n        (7, 166),\n        (224, 0),\n        (20, 192),\n        (27, 132),\n        (28, 160),\n        (17, 149),\n        (19, 193),\n        (96, 132),\n        (76, 164),\n        (208, 0),\n        (80, 192),\n        (78, 132),\n        (96, 160),\n        (27, 144),\n        (24, 193),\n        (140, 0),\n        (96, 178),\n        (17, 141),\n        (12, 193),\n        (224, 0),\n        (14, 161),\n        (17, 141),\n        (151, 0),\n        (14, 132),\n        (16, 165),\n        (96, 137),\n        (13, 193),\n        (155, 0),\n        (20, 161),\n        (29, 141),\n        (23, 192),\n        (24, 132),\n        (27, 178),\n        (10, 133),\n        (96, 192),\n        (140, 0),\n        (14, 180),\n        (17, 133),\n        (16, 192),\n        (144, 0),\n        (11, 163),\n        (13, 141),\n        (96, 192),\n        (17, 132),\n        (12, 178),\n        (96, 141),\n        (28, 192),\n        (27, 132),\n        (27, 130),\n        (18, 141),\n        (96, 193),\n        (31, 132),\n        (96, 181),\n        (13, 140),\n        (23, 193),\n        (224, 0),\n        (27, 166),\n        (142, 0),\n        (27, 192),\n        (24, 132),\n        (12, 183),\n        (96, 133),\n        (84, 192),\n        (14, 132),\n        (27, 178),\n        (10, 140),\n        (155, 0),\n        (9, 132),\n        (17, 160),\n        (56, 133),\n        (96, 192),\n        (82, 132),\n        (13, 160),\n        (27, 137),\n        (20, 193),\n        (139, 0),\n        (28, 161),\n        (145, 0),\n        (19, 192),\n        (118, 132),\n        (115, 165),\n        (20, 132),\n        (145, 0),\n        (14, 132),\n        (12, 167),\n        (146, 0),\n        (17, 193),\n        (29, 132),\n        (96, 176),\n        (28, 144),\n        (27, 193),\n        (140, 0),\n        (31, 180),\n        (148, 0),\n        (27, 192),\n        (14, 132),\n        (83, 160),\n        (18, 137),\n        (17, 193),\n        (23, 132),\n        (13, 165),\n        (13, 145),\n        (151, 0),\n        (147, 0),\n        (27, 178),\n        (96, 137),\n        (19, 193),\n        (159, 0),\n        (14, 160),\n        (25, 148),\n        (17, 193),\n        (142, 0),\n        (16, 180),\n        (27, 136),\n        (14, 193),\n        (224, 0),\n        (17, 178),\n        (12, 144),\n        (224, 0),\n        (28, 132),\n        (27, 160),\n        (13, 141),\n        (11, 193),\n        (96, 132),\n        (27, 165),\n        (30, 140),\n        (224, 0),\n        (146, 0),\n        (31, 165),\n        (29, 129),\n        (96, 192),\n        (140, 0),\n        (31, 161),\n        (24, 145),\n        (140, 0),\n        (96, 132),\n        (27, 165),\n        (29, 140),\n        (31, 192),\n        (154, 0),\n        (14, 161),\n        (27, 145),\n        (140, 0),\n        (18, 132),\n        (23, 167),\n        (96, 140),\n        (21, 129),\n        (14, 132),\n        (17, 165),\n        (9, 137),\n        (12, 193),\n        (155, 0),\n        (18, 161),\n        (96, 141),\n        (27, 192),\n        (148, 0),\n        (29, 178),\n        (23, 133),\n        (24, 192),\n        (155, 0),\n        (10, 180),\n        (96, 133),\n        (28, 192),\n        (14, 132),\n        (31, 130),\n        (28, 129),\n        (18, 193),\n        (31, 132),\n        (12, 180),\n        (13, 144),\n        (96, 193),\n        (31, 132),\n        (96, 160),\n        (13, 141),\n        (27, 193),\n        (18, 132),\n        (23, 181),\n        (26, 140),\n        (27, 193),\n        (156, 0),\n        (96, 166),\n        (79, 141),\n        (211, 0),\n        (76, 132),\n        (77, 160),\n        (75, 133),\n        (206, 0),\n        (182, 0),\n        (96, 129),\n        (59, 133),\n        (191, 0),\n        (173, 0),\n    ];\n\n    assert!(block_data.len() <= BLOCK_SIZE);\n\n    let mut decoded_block: Vec<u8> = vec![];\n\n    for (i, byte) in block_data.iter().enumerate() {\n        let encoding = encoding_table[i];\n\n        decoded_block.push(((((*byte as usize) + encoding.0) ^ encoding.1) % 256) as u8);\n    }\n\n    decoded_block\n}\n"
  },
  {
    "path": "src/extractors/bmp.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::bmp::{get_dib_header_size, parse_bmp_file_header};\n\n/// Defines the internal extractor function for carving out GIF images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::bmp::bmp_extractor;\n///\n/// match bmp_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn bmp_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_bmp_image),\n        ..Default::default()\n    }\n}\n\npub fn extract_bmp_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"image.bmp\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n    result.success = false;\n\n    // Parse the bmp_file_header\n    if let Ok(bmp_file_header) = parse_bmp_file_header(&file_data[offset..]) {\n        // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader\n        // The size of the BMP file header\n        const BMP_FILE_HEADER_SIZE: usize = 14;\n\n        // Retrieve the size of the header following the BMP file header\n        if let Ok(bmp_header_size) =\n            get_dib_header_size(&file_data[(offset + BMP_FILE_HEADER_SIZE)..])\n        {\n            // The offset that points to the image data cannot point into the second header\n            if bmp_file_header.bitmap_bits_offset >= (BMP_FILE_HEADER_SIZE + bmp_header_size) {\n                // If it was parsed successfully, get the file size\n                result.size = Some(bmp_file_header.size);\n                result.success = true;\n\n                if output_directory.is_some() {\n                    let chroot = Chroot::new(output_directory);\n                    result.success =\n                        chroot.carve_file(OUTFILE_NAME, file_data, offset, bmp_file_header.size);\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/bzip2.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse bzip2::{Decompress, Status};\n\n/// Defines the internal extractor function for decompressing BZIP2 files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::bzip2::bzip2_extractor;\n///\n/// match bzip2_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn bzip2_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(bzip2_decompressor),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for decompressing BZIP2 data\npub fn bzip2_decompressor(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Size of decompression buffer\n    const BLOCK_SIZE: usize = 900 * 1024;\n    // Output file for decompressed data\n    const OUTPUT_FILE_NAME: &str = \"decompressed.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    let mut bytes_written: usize = 0;\n    let mut stream_offset: usize = 0;\n    let bzip2_data = &file_data[offset..];\n    let mut decompressed_buffer = [0; BLOCK_SIZE];\n    let mut decompressor = Decompress::new(false);\n    let available_data = bzip2_data.len();\n    let mut previous_offset = None;\n\n    /*\n     * Loop through all compressed data and decompress it.\n     *\n     * This has a significant performance hit since 1) decompression takes time, and 2) data is\n     * decompressed once during signature validation and a second time during extraction (if extraction\n     * was requested).\n     *\n     * The advantage is that not only are we 100% sure that this data is valid BZIP2 data, but we\n     * can also determine the exact size of the BZIP2 data.\n     */\n    while is_offset_safe(available_data, stream_offset, previous_offset) {\n        previous_offset = Some(stream_offset);\n\n        // Decompress a block of data\n        match decompressor.decompress(&bzip2_data[stream_offset..], &mut decompressed_buffer) {\n            Err(_) => {\n                // Break on decompression error\n                break;\n            }\n            Ok(status) => {\n                match status {\n                    Status::RunOk => break,\n                    Status::FlushOk => break,\n                    Status::FinishOk => break,\n                    Status::MemNeeded => break,\n                    Status::Ok => {\n                        stream_offset = decompressor.total_in() as usize;\n                    }\n                    Status::StreamEnd => {\n                        result.success = true;\n                        result.size = Some(decompressor.total_in() as usize);\n                    }\n                }\n\n                // Decompressed a block of data, if extraction was requested write the decompressed block to the output file\n                if output_directory.is_some() {\n                    let n: usize = (decompressor.total_out() as usize) - bytes_written;\n\n                    let chroot = Chroot::new(output_directory);\n                    if !chroot.append_to_file(OUTPUT_FILE_NAME, &decompressed_buffer[0..n]) {\n                        // If writing data to file fails, break\n                        break;\n                    }\n\n                    bytes_written += n;\n                }\n\n                // If everything has been processed successfully, we're done; break.\n                if result.success {\n                    break;\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/cab.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the cabextract utility to extract MS CAB archives\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::cab::cab_extractor;\n///\n/// match cab_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn cab_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"cabextract\".to_string()),\n        extension: \"cab\".to_string(),\n        arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/common.rs",
    "content": "use crate::signatures::common::SignatureResult;\nuse log::{debug, error, info, warn};\nuse serde::{Deserialize, Serialize};\nuse std::fs;\nuse std::io::Write;\nuse std::path;\nuse std::process;\nuse walkdir::WalkDir;\n\n#[cfg(windows)]\nuse std::os::windows;\n\n#[cfg(unix)]\nuse std::os::unix;\n#[cfg(unix)]\nuse std::os::unix::fs::PermissionsExt;\n\n/// This contstant in command line arguments will be replaced with the path to the input file\npub const SOURCE_FILE_PLACEHOLDER: &str = \"%e\";\n\n/// Return value of InternalExtractor upon error\n#[derive(Debug, Clone)]\npub struct ExtractionError;\n\n/// Built-in internal extractors must provide a function conforming to this definition.\n/// Arguments: file_data, offset, output_directory.\npub type InternalExtractor = fn(&[u8], usize, Option<&str>) -> ExtractionResult;\n\n/// Enum to define either an Internal or External extractor type\n#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd)]\npub enum ExtractorType {\n    External(String),\n    Internal(InternalExtractor),\n    #[default]\n    None,\n}\n\n/// Describes extractors, both external and internal\n#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]\npub struct Extractor {\n    /// External command or internal function to execute\n    pub utility: ExtractorType,\n    /// File extension expected by an external command\n    pub extension: String,\n    /// Arguments to pass to the external command\n    pub arguments: Vec<String>,\n    /// A list of successful exit codes for the external command\n    pub exit_codes: Vec<i32>,\n    /// Set to true to disable recursion into this extractor's extracted files\n    pub do_not_recurse: bool,\n}\n\n/// Stores information about a completed extraction\n#[derive(Debug, Clone, Default, Serialize, Deserialize)]\npub struct ExtractionResult {\n    /// Size of the data consumed during extraction, if known; should be populated by the constructor\n    pub size: Option<usize>,\n    /// Extractor success status; should be populated by the constructor\n    pub success: bool,\n    /// Extractor name, automatically populated by extractors::common::execute\n    pub extractor: String,\n    /// Set to true to disable recursion into this extractor's extracted files.\n    /// Automatically populated with the corresponding Extractor.do_not_recurse field by extractors::common::execute.\n    pub do_not_recurse: bool,\n    /// The output directory where the extractor dropped its files, automatically populated by extractors::common::execute\n    pub output_directory: String,\n}\n\n/// Stores information about external extractor processes. For internal use only.\n#[derive(Debug)]\npub struct ProcInfo {\n    pub child: process::Child,\n    pub exit_codes: Vec<i32>,\n    pub carved_file: String,\n}\n\n/// Provides chroot-like functionality for internal extractors\n#[derive(Debug, Default, Clone)]\npub struct Chroot {\n    /// The chroot directory passed to Chroot::new\n    pub chroot_directory: String,\n}\n\nimpl Chroot {\n    /// Create a new chrooted instance. All file paths will be effectively chrooted in the specified directory path.\n    /// The chroot directory path will be created if it does not already exist.\n    ///\n    /// If no directory path is specified, the chroot directory will be `/`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(&std::env::temp_dir())\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(&chroot.chroot_directory, &chroot_dir);\n    /// assert_eq!(std::path::Path::new(&chroot_dir).exists(), true);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// ```\n    pub fn new(chroot_directory: Option<&str>) -> Chroot {\n        let mut chroot_instance = Chroot {\n            ..Default::default()\n        };\n\n        match chroot_directory {\n            None => {\n                // Default path is '/'\n                chroot_instance.chroot_directory = path::MAIN_SEPARATOR.to_string();\n            }\n            Some(chroot_dir) => {\n                // Attempt to ensure that the specified path is absolute. If this fails, just use the path as given.\n                match path::absolute(chroot_dir) {\n                    Ok(pathbuf) => {\n                        chroot_instance.chroot_directory = pathbuf.display().to_string();\n                    }\n                    Err(_) => {\n                        chroot_instance.chroot_directory = chroot_dir.to_string();\n                    }\n                }\n            }\n        }\n\n        // Create the chroot directory if it does not exist\n        if !path::Path::new(&chroot_instance.chroot_directory).exists() {\n            match fs::create_dir_all(&chroot_instance.chroot_directory) {\n                Ok(_) => {\n                    debug!(\n                        \"Created new chroot directory {}\",\n                        chroot_instance.chroot_directory\n                    );\n                }\n                Err(e) => {\n                    error!(\n                        \"Failed to create chroot directory {}: {}\",\n                        chroot_instance.chroot_directory, e\n                    );\n                }\n            }\n        }\n\n        chroot_instance\n    }\n\n    /// Joins two paths, ensuring that the final path does not traverse outside of the chroot directory.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    /// use std::path::MAIN_SEPARATOR;\n    ///\n    /// let chroot_dir = std::path::Path::new(&std::env::temp_dir())\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// let dir_name = \"etc\";\n    /// let file_name = \"passwd\";\n    /// let abs_path = format!(\"{}{}{}{}\", MAIN_SEPARATOR, dir_name, MAIN_SEPARATOR, file_name);\n    /// let abs_path_dir = format!(\"{}{}\", MAIN_SEPARATOR, dir_name);\n    /// let rel_path_dir = format!(\"..{}..{}..{}{}\", MAIN_SEPARATOR, MAIN_SEPARATOR, MAIN_SEPARATOR, dir_name);\n    /// let abs_path_file = format!(\"{}{}\", MAIN_SEPARATOR, file_name);\n    /// let rel_path_file = format!(\"..{}..{}..{}{}\", MAIN_SEPARATOR, MAIN_SEPARATOR, MAIN_SEPARATOR, file_name);\n    ///\n    /// let path1 = chroot.safe_path_join(&abs_path_dir, file_name);\n    /// let expected_path1 = std::path::Path::new(&chroot_dir).join(dir_name).join(file_name);\n    ///\n    /// let path2 = chroot.safe_path_join(&abs_path_dir, &rel_path_file);\n    /// let expected_path2 = std::path::Path::new(&chroot_dir).join(file_name);\n    ///\n    /// let path3 = chroot.safe_path_join(&rel_path_dir, &abs_path_file);\n    /// let expected_path3 = std::path::Path::new(&chroot_dir).join(dir_name).join(file_name);\n    ///\n    /// let path4 = chroot.safe_path_join(&chroot_dir, &abs_path);\n    /// let expected_path4 = std::path::Path::new(&chroot_dir).join(dir_name).join(file_name);\n    ///\n    /// assert_eq!(path1, expected_path1.display().to_string());\n    /// assert_eq!(path2, expected_path2.display().to_string());\n    /// assert_eq!(path3, expected_path3.display().to_string());\n    /// assert_eq!(path4, expected_path4.display().to_string());\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// ```\n    pub fn safe_path_join(&self, path1: impl Into<String>, path2: impl Into<String>) -> String {\n        // Join and sanitize both paths; retain the leading '/' (if there is one)\n        let mut joined_path: String = self.sanitize_path(\n            &format!(\"{}{}{}\", path1.into(), path::MAIN_SEPARATOR, path2.into()),\n            true,\n        );\n\n        // If the joined path does not start with the chroot directory,\n        // prepend the chroot directory to the final joined path.\n        // on Windows: If no chroot directory is specified, skip the operation\n        if cfg!(windows) && self.chroot_directory == path::MAIN_SEPARATOR.to_string() {\n            // do nothing and skip\n        } else if !joined_path.starts_with(&self.chroot_directory) {\n            joined_path = format!(\n                \"{}{}{}\",\n                self.chroot_directory,\n                path::MAIN_SEPARATOR,\n                joined_path\n            );\n        }\n\n        self.strip_double_slash(&joined_path)\n    }\n\n    /// Given a file path, returns a sanitized path that is chrooted inside the specified chroot directory.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(&std::env::temp_dir())\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_name = \"test.txt\";\n    ///\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    /// let path = chroot.chrooted_path(file_name);\n    ///\n    /// assert_eq!(path, std::path::Path::new(&chroot_dir).join(file_name).display().to_string());\n    /// ```\n    pub fn chrooted_path(&self, file_path: impl Into<String>) -> String {\n        self.safe_path_join(file_path, \"\".to_string())\n    }\n\n    /// Creates a regular file in the chrooted directory and writes the provided data to it.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_213_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let file_data: &[u8] = b\"foobar\";\n    ///\n    /// let file_name = \"created_file.txt\";\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_file(file_name, file_data), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, std::str::from_utf8(file_data)?);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_213_0(); }\n    /// ```\n    pub fn create_file(&self, file_path: impl Into<String>, file_data: &[u8]) -> bool {\n        let safe_file_path: String = self.chrooted_path(file_path);\n\n        if !path::Path::new(&safe_file_path).exists() {\n            match fs::write(safe_file_path.clone(), file_data) {\n                Ok(_) => {\n                    return true;\n                }\n                Err(e) => {\n                    error!(\"Failed to write data to {safe_file_path}: {e}\");\n                }\n            }\n        } else {\n            error!(\"Failed to create file {safe_file_path}: path already exists\");\n        }\n\n        false\n    }\n\n    /// Carve data and write it to a new file.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_255_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// const CARVE_SIZE: usize = 6;\n    ///\n    /// let data: &[u8] = b\"foobarJUNK\";\n    ///\n    /// let file_name = \"carved_file.txt\";\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.carve_file(file_name, data, 0, CARVE_SIZE), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, std::str::from_utf8(&data[0..CARVE_SIZE])?);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// } _doctest_main_src_extractors_common_rs_255_0(); }\n    /// ```\n    pub fn carve_file(\n        &self,\n        file_path: impl Into<String>,\n        data: &[u8],\n        start: usize,\n        size: usize,\n    ) -> bool {\n        let mut retval: bool = false;\n\n        if let Some(file_data) = data.get(start..start + size) {\n            retval = self.create_file(file_path, file_data);\n        } else {\n            error!(\n                \"Failed to create file {}: data offset/size are invalid\",\n                file_path.into()\n            );\n        }\n\n        retval\n    }\n\n    /// Creates a device file in the chroot directory.\n    ///\n    /// Note that this does *not* create a real device file, just a regular file containing the device file info.\n    fn create_device(\n        &self,\n        file_path: impl Into<String>,\n        device_type: &str,\n        major: usize,\n        minor: usize,\n    ) -> bool {\n        let device_file_contents: String = format!(\"{device_type} {major} {minor}\");\n        self.create_file(file_path, &device_file_contents.clone().into_bytes())\n    }\n\n    /// Creates a character device file in the chroot directory.\n    ///\n    /// Note that this does *not* create a real character device, just a regular file containing the text `c <major> <minor>`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_312_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let dev_major: usize = 1;\n    /// let dev_minor: usize = 2;\n    /// let file_name = \"char_device\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_character_device(file_name, dev_major, dev_minor), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, \"c 1 2\");\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_312_0(); }\n    /// ```\n    pub fn create_character_device(\n        &self,\n        file_path: impl Into<String>,\n        major: usize,\n        minor: usize,\n    ) -> bool {\n        self.create_device(file_path, \"c\", major, minor)\n    }\n\n    /// Creates a block device file in the chroot directory.\n    ///\n    /// Note that this does *not* create a real block device, just a regular file containing the text `b <major> <minor>`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_345_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let dev_major: usize = 1;\n    /// let dev_minor: usize = 2;\n    /// let file_name = \"block_device\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_block_device(file_name, dev_major, dev_minor), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, \"b 1 2\");\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_345_0(); }\n    /// ```\n    pub fn create_block_device(\n        &self,\n        file_path: impl Into<String>,\n        major: usize,\n        minor: usize,\n    ) -> bool {\n        self.create_device(file_path, \"b\", major, minor)\n    }\n\n    /// Creates a fifo file in the chroot directory.\n    ///\n    /// Note that this does *not* create a real fifo, just a regular file containing the text `fifo`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_377_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_name = \"fifo_file\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_fifo(file_name), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, \"fifo\");\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_377_0(); }\n    /// ```\n    pub fn create_fifo(&self, file_path: impl Into<String>) -> bool {\n        self.create_file(file_path, b\"fifo\")\n    }\n\n    /// Creates a socket file in the chroot directory.\n    ///\n    /// Note that this does *not* create a real socket, just a regular file containing the text `socket`.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_401_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_name = \"socket_file\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_socket(file_name), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, \"socket\");\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_401_0(); }\n    /// ```\n    pub fn create_socket(&self, file_path: impl Into<String>) -> bool {\n        self.create_file(file_path, b\"socket\")\n    }\n\n    /// Append the provided data to the specified file in the chroot directory.\n    ///\n    /// If the specified file does not exist, it will be created.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_426_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_data: &[u8] = b\"foobar\";\n    /// let file_name = \"append.txt\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.append_to_file(file_name, file_data), true);\n    /// assert_eq!(std::fs::read_to_string(std::path::Path::new(&chroot_dir).join(file_name))?, std::str::from_utf8(file_data)?);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_426_0(); }\n    /// ```\n    pub fn append_to_file(&self, file_path: impl Into<String>, data: &[u8]) -> bool {\n        let safe_file_path: String = self.chrooted_path(file_path);\n\n        if !self.is_symlink(&safe_file_path) {\n            match fs::OpenOptions::new()\n                .create(true)\n                .append(true)\n                .open(safe_file_path.clone())\n            {\n                Err(e) => {\n                    error!(\"Failed to open file '{safe_file_path}' for appending: {e}\");\n                }\n                Ok(mut fp) => match fp.write(data) {\n                    Err(e) => {\n                        error!(\"Failed to append to file '{safe_file_path}': {e}\");\n                    }\n                    Ok(_) => {\n                        return true;\n                    }\n                },\n            }\n        } else {\n            error!(\"Attempted to append data to a symlink: {safe_file_path}\");\n        }\n\n        false\n    }\n\n    /// Creates a directory in the chroot directory.\n    ///\n    /// Equivalent to mkdir -p.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let dir_name = \"my_directory\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_directory(dir_name), true);\n    /// assert_eq!(std::path::Path::new(&chroot_dir).join(dir_name).exists(), true);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// ```\n    pub fn create_directory(&self, dir_path: impl Into<String>) -> bool {\n        let safe_dir_path: String = self.chrooted_path(dir_path);\n\n        match fs::create_dir_all(safe_dir_path.clone()) {\n            Ok(_) => {\n                return true;\n            }\n            Err(e) => {\n                error!(\"Failed to create output directory {safe_dir_path}: {e}\");\n            }\n        }\n\n        false\n    }\n\n    /// Delete a directory in the chroot directory.\n    ///\n    /// Equivalent to rm -rf.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let dir_name = \"my_directory\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_directory(dir_name), true);\n    /// assert_eq!(chroot.remove_directory(dir_name), true);\n    /// assert_eq!(chroot.remove_directory(\"i_dont_exist\"), true);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// ```\n    pub fn remove_directory(&self, dir_path: impl Into<String>) -> bool {\n        let safe_dir_path: String = self.chrooted_path(dir_path);\n\n        match fs::exists(safe_dir_path.clone()) {\n            Ok(dir_exists) => {\n                if !dir_exists {\n                    return true;\n                }\n            }\n            Err(e) => {\n                error!(\"Failed to check if directory {safe_dir_path} exists: {e:?}\");\n                return false;\n            }\n        }\n\n        match fs::remove_dir_all(safe_dir_path.clone()) {\n            Ok(_) => return true,\n            Err(e) => error!(\"Failed to delete directory {safe_dir_path}: {e}\"),\n        }\n\n        false\n    }\n\n    /// Set executable permissions on an existing file in the chroot directory.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let file_name = \"runme.exe\";\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    /// chroot.create_file(file_name, b\"AAAA\");\n    ///\n    /// assert_eq!(chroot.make_executable(file_name), true);\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// ```\n    #[allow(dead_code)]\n    pub fn make_executable(&self, file_path: impl Into<String>) -> bool {\n        // Make the file globally executable\n        const UNIX_EXEC_FLAG: u32 = 1;\n\n        let safe_file_path: String = self.chrooted_path(file_path);\n\n        match fs::metadata(safe_file_path.clone()) {\n            Err(e) => {\n                error!(\"Failed to get permissions for file {safe_file_path}: {e}\");\n            }\n            Ok(_metadata) => {\n                #[cfg(unix)]\n                {\n                    let mut permissions = _metadata.permissions();\n                    let mode = permissions.mode() | UNIX_EXEC_FLAG;\n                    permissions.set_mode(mode);\n\n                    match fs::set_permissions(&safe_file_path, permissions) {\n                        Err(e) => {\n                            error!(\"Failed to set permissions for file {safe_file_path}: {e}\");\n                        }\n                        Ok(_) => {\n                            return true;\n                        }\n                    }\n                }\n                #[cfg(windows)]\n                {\n                    return true;\n                }\n            }\n        }\n\n        false\n    }\n\n    /// Creates a symbolic link in the chroot directory, named `symlink_path`, which points to `target_path`.\n    ///\n    /// Note that both the symlink and target paths will be sanitized to stay in the chroot directory.\n    /// Both the target path will be converted into a path relative to the symlink file path.\n    ///\n    /// ## Example\n    ///\n    /// ```\n    /// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_extractors_common_rs_571_0() -> Result<(), Box<dyn std::error::Error>> {\n    /// use binwalk::extractors::common::Chroot;\n    ///\n    /// let chroot_dir = std::path::Path::new(\"tests\")\n    ///     .join(\"binwalk_unit_tests\")\n    ///     .display()\n    ///     .to_string();\n    ///\n    /// let symlink_name = \"symlink\";\n    /// let target_path = \"target\";\n    ///\n    /// let expected_symlink_path = std::path::Path::new(&chroot_dir).join(symlink_name);\n    /// let expected_target_path = std::path::Path::new(&chroot_dir).join(target_path);\n    ///\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// let chroot = Chroot::new(Some(&chroot_dir));\n    ///\n    /// assert_eq!(chroot.create_symlink(symlink_name, target_path), true);\n    /// assert_eq!(std::fs::canonicalize(expected_symlink_path)?.to_str(), expected_target_path.to_str());\n    /// # std::fs::remove_dir_all(&chroot_dir);\n    /// # Ok(())\n    /// # } _doctest_main_src_extractors_common_rs_571_0(); }\n    /// ```\n    pub fn create_symlink(\n        &self,\n        symlink_path: impl Into<String>,\n        target_path: impl Into<String>,\n    ) -> bool {\n        let target = target_path.into();\n        let symlink = symlink_path.into();\n\n        // Chroot the symlink file path and create a Path object\n        let safe_symlink = self.chrooted_path(&symlink);\n        let safe_symlink_path = path::Path::new(&safe_symlink);\n\n        // Normalize the symlink target path to a chrooted absolute path\n        let safe_target = if target.starts_with(path::MAIN_SEPARATOR) {\n            // If the target path is absolute, just chroot it inside the chroot directory\n            self.chrooted_path(&target)\n        } else {\n            // Get the symlink file's parent directory path\n            let relative_dir: String = match safe_symlink_path.parent() {\n                None => {\n                    // There is no parent, or parent is the root directory; assume the root directory\n                    path::MAIN_SEPARATOR.to_string()\n                }\n                Some(parent_dir) => {\n                    // Got the parent directory\n                    parent_dir.display().to_string()\n                }\n            };\n\n            // Join the target path with its relative directory, ensuring it does not traverse outside\n            // the specified chroot directory\n            self.safe_path_join(&relative_dir, &target)\n        };\n\n        // Remove the chroot directory from the target and symlink paths.\n        // This results in each being an absolute path that is relative to the chroot directory,\n        // e.g., '/my_chroot_dir/bin/busybox' -> '/bin/busybox'.\n        //\n        // Note: need at least one leading '/', so if the chroot directory is just '/', just use the string as-is.\n        let mut safe_target_rel_path = if self.chroot_directory == path::MAIN_SEPARATOR.to_string()\n        {\n            safe_target.clone()\n        } else {\n            safe_target.replacen(&self.chroot_directory, \"\", 1)\n        };\n\n        let safe_symlink_rel_path = if self.chroot_directory == path::MAIN_SEPARATOR.to_string() {\n            safe_symlink.clone()\n        } else {\n            safe_symlink.replacen(&self.chroot_directory, \"\", 1)\n        };\n\n        // Count the number of path separators (minus the leading one) and an '../' to the target\n        // path for each; e.g., '/bin/busybox' -> '..//bin/busybox'.\n        for _i in 0..safe_symlink_rel_path.matches(path::MAIN_SEPARATOR).count() - 1 {\n            safe_target_rel_path = format!(\"..{}{}\", path::MAIN_SEPARATOR, safe_target_rel_path);\n        }\n\n        // Add a '.' at the beginning of any paths that start with '/', e.g., '/tmp' -> './tmp'.\n        if safe_target_rel_path.starts_with(path::MAIN_SEPARATOR) {\n            safe_target_rel_path = format!(\".{safe_target_rel_path}\");\n        }\n\n        // Replace any instances of '//' with '/'\n        safe_target_rel_path = self.strip_double_slash(&safe_target_rel_path);\n\n        // The target path is now a safely chrooted path that is relative to the symlink file path.\n        // Ex:\n        //\n        //     Original symlink: \"/my_chroot_dir/usr/sbin/ls\" is a symlink to \"/bin/busybox\"\n        //     Safe relative symlink: \"/my_chroot_dir/usr/sbin/ls\" is a symlink to \"./../../bin/busybox\"\n        let safe_target_path = path::Path::new(&safe_target_rel_path);\n\n        #[cfg(unix)]\n        {\n            match unix::fs::symlink(safe_target_path, safe_symlink_path) {\n                Ok(_) => true,\n                Err(e) => {\n                    error!(\"Failed to create symlink from {symlink} -> {target}: {e}\");\n                    false\n                }\n            }\n        }\n        #[cfg(windows)]\n        {\n            // let sym = match safe_target_path.is_dir() {\n            //     true => windows::fs::symlink_dir(safe_target_path, safe_symlink_path),\n            //     false => windows::fs::symlink_file(safe_target_path, safe_symlink_path),\n            // };\n\n            match windows::fs::symlink_dir(safe_target_path, safe_symlink_path) {\n                Ok(_) => {\n                    return true;\n                }\n                Err(e) => {\n                    error!(\n                        \"Failed to create symlink from {} -> {}: {}\",\n                        symlink, target, e\n                    );\n                    return false;\n                }\n            }\n        }\n    }\n\n    /// Returns true if the file path is a symlink.\n    fn is_symlink(&self, file_path: &str) -> bool {\n        if let Ok(metadata) = fs::symlink_metadata(file_path) {\n            return metadata.file_type().is_symlink();\n        }\n\n        false\n    }\n\n    /// Replace `//` with `/`. This is for asthetics only.\n    fn strip_double_slash(&self, path: &str) -> String {\n        let mut stripped_path = path.to_owned();\n        let single_slash = path::MAIN_SEPARATOR.to_string();\n        let double_slash = format!(\"{single_slash}{single_slash}\");\n\n        while stripped_path.contains(&double_slash) {\n            stripped_path = stripped_path.replace(&double_slash, &single_slash);\n        }\n\n        stripped_path\n    }\n\n    /// Interprets a given path containing '..' directories.\n    fn sanitize_path(&self, file_path: &str, preserve_root_path_sep: bool) -> String {\n        const DIR_TRAVERSAL: &str = \"..\";\n\n        let mut exclude_indicies: Vec<usize> = vec![];\n        let mut sanitized_path: String = \"\".to_string();\n\n        if preserve_root_path_sep && file_path.starts_with(path::MAIN_SEPARATOR) {\n            sanitized_path = path::MAIN_SEPARATOR.to_string();\n        }\n\n        // Split the file path on '/'\n        let path_parts: Vec<&str> = file_path.split(path::MAIN_SEPARATOR).collect();\n\n        // Loop through each part of the file path\n        for (i, path_part) in path_parts.iter().enumerate() {\n            // If this part of the path is '..', don't include it in the final sanitized path\n            if *path_part == DIR_TRAVERSAL {\n                exclude_indicies.push(i);\n                if i > 0 {\n                    // Walk backwards through the path parts until a non-excluded part is found, then mark that part for exclusion as well\n                    let mut j = i - 1;\n                    while j > 0 && exclude_indicies.contains(&j) {\n                        j -= 1;\n                    }\n                    exclude_indicies.push(j);\n                }\n            // If this part of the path is an empty string, don't include that either (happens if the original file path has '//' in it)\n            } else if path_part.is_empty() {\n                exclude_indicies.push(i);\n            }\n        }\n\n        // Concatenate each non-excluded part of the file path, with each part separated by '/'\n        for (i, path_part) in path_parts.iter().enumerate() {\n            if !exclude_indicies.contains(&i) {\n                #[cfg(windows)]\n                {\n                    // on Windows: in the first loop run, we cannot really prepend a '\\' to drive letters like 'C:'\n                    if sanitized_path.is_empty() {\n                        sanitized_path = path_part.to_string();\n                        continue;\n                    }\n                }\n                sanitized_path = format!(\"{}{}{}\", sanitized_path, path::MAIN_SEPARATOR, path_part);\n            }\n        }\n\n        self.strip_double_slash(&sanitized_path)\n    }\n}\n\n/// Recursively walks a given directory and returns a list of regular non-zero size files in the given directory path.\n#[allow(dead_code)]\npub fn get_extracted_files(directory: &str) -> Vec<String> {\n    let mut regular_files: Vec<String> = vec![];\n\n    for entry in WalkDir::new(directory).into_iter() {\n        match entry {\n            Err(_e) => continue,\n            Ok(entry) => {\n                let entry_path = entry.path();\n                // Query file metadata *without* following symlinks\n                match fs::symlink_metadata(entry_path) {\n                    Err(_e) => continue,\n                    Ok(md) => {\n                        // Only interested in non-empty, regular files\n                        if md.is_file() && md.len() > 0 {\n                            regular_files.push(entry_path.display().to_string());\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    regular_files\n}\n\n/// Executes an extractor for the provided SignatureResult.\npub fn execute(\n    file_data: &[u8],\n    file_path: &str,\n    signature: &SignatureResult,\n    extractor: &Option<Extractor>,\n) -> ExtractionResult {\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Create an output directory for the extraction\n    if let Ok(output_directory) = create_output_directory(file_path, signature.offset) {\n        // Make sure a defalut extractor was actually defined (this function should not be called if signature.extractor is None)\n        match &extractor {\n            None => {\n                error!(\n                    \"Attempted to extract {} data, but no extractor is defined!\",\n                    signature.name\n                );\n            }\n\n            Some(default_extractor) => {\n                let extractor_definition: Extractor;\n\n                // If the signature result specified a preferred extractor, use that instead of the default signature extractor\n                if let Some(preferred_extractor) = &signature.preferred_extractor {\n                    extractor_definition = preferred_extractor.clone();\n                } else {\n                    extractor_definition = default_extractor.clone();\n                }\n\n                // Decide how to execute the extractor depending on the extractor type\n                match &extractor_definition.utility {\n                    ExtractorType::None => {\n                        error!(\n                            \"Signature {}: an extractor of type None is invalid!\",\n                            signature.name\n                        );\n                    }\n\n                    ExtractorType::Internal(func) => {\n                        debug!(\"Executing internal {} extractor\", signature.name);\n                        // Run the internal extractor function\n                        result = func(file_data, signature.offset, Some(&output_directory));\n                        // Set the extractor name to \"<signature name>_built_in\"\n                        result.extractor = format!(\"{}_built_in\", signature.name);\n                    }\n\n                    ExtractorType::External(cmd) => {\n                        // Spawn the external extractor command\n                        match spawn(\n                            file_data,\n                            file_path,\n                            &output_directory,\n                            signature,\n                            extractor_definition.clone(),\n                        ) {\n                            Err(e) => {\n                                error!(\n                                    \"Failed to spawn external extractor for '{}' signature: {}\",\n                                    signature.name, e\n                                );\n                            }\n\n                            Ok(proc_info) => {\n                                // Wait for the external process to exit\n                                match proc_wait(proc_info) {\n                                    Err(_) => {\n                                        warn!(\"External extractor failed!\");\n                                    }\n                                    Ok(ext_result) => {\n                                        result = ext_result;\n                                        // Set the extractor name to the name of the extraction utility\n                                        result.extractor = cmd.to_string();\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                // Populate these ExtractionResult fields automatically for all extractors\n                result.output_directory = output_directory.clone();\n                result.do_not_recurse = extractor_definition.do_not_recurse;\n\n                // If the extractor reported success, make sure it extracted something other than just an empty file\n                if result.success && !was_something_extracted(&result.output_directory) {\n                    result.success = false;\n                    warn!(\"Extractor exited successfully, but no data was extracted\");\n                }\n            }\n        }\n\n        // Clean up extractor's output directory if extraction failed\n        if !result.success {\n            if let Err(e) = fs::remove_dir_all(&output_directory) {\n                warn!(\n                    \"Failed to clean up extraction directory {output_directory} after extraction failure: {e}\"\n                );\n            }\n        }\n    }\n\n    result\n}\n\n/// Spawn an external extractor process.\nfn spawn(\n    file_data: &[u8],\n    file_path: &str,\n    output_directory: &str,\n    signature: &SignatureResult,\n    mut extractor: Extractor,\n) -> Result<ProcInfo, std::io::Error> {\n    let chroot = Chroot::new(None);\n\n    // This function *only* handles execution of external extraction utilities; internal extractors must be invoked directly\n    let command = match &extractor.utility {\n        ExtractorType::External(cmd) => cmd.clone(),\n        ExtractorType::Internal(_ext) => {\n            error!(\"Tried to run an internal extractor as an external command!\");\n            return Err(std::io::Error::other(\n                \"attempt to execute an internal extractor as an external command\",\n            ));\n        }\n        ExtractorType::None => {\n            error!(\"An extractor command was defined, but is set to None!\");\n            return Err(std::io::Error::other(\n                \"invalid external command of type None\",\n            ));\n        }\n    };\n\n    // Carved file path will be <output directory>/<signature.name>_<hex offset>.<extractor.extension>\n    let carved_file = format!(\n        \"{}{}{}_{:X}.{}\",\n        output_directory,\n        path::MAIN_SEPARATOR,\n        signature.name,\n        signature.offset,\n        extractor.extension\n    );\n    info!(\n        \"Carving data from {} {:#X}..{:#X} to {}\",\n        file_path,\n        signature.offset,\n        signature.offset + signature.size,\n        carved_file\n    );\n\n    // If the entirety of the source file is this one file type, no need to carve a copy of it, just create a symlink\n    if signature.offset == 0 && signature.size == file_data.len() {\n        if !chroot.create_symlink(&carved_file, file_path) {\n            return Err(std::io::Error::other(\n                \"Failed to create carved file symlink\",\n            ));\n        }\n    } else {\n        // Copy file data to carved file path\n        if !chroot.carve_file(&carved_file, file_data, signature.offset, signature.size) {\n            return Err(std::io::Error::other(\"Failed to carve data to disk\"));\n        }\n    }\n\n    // Replace all \"%e\" command arguments with the path to the carved file\n    for i in 0..extractor.arguments.len() {\n        if extractor.arguments[i] == SOURCE_FILE_PLACEHOLDER {\n            extractor.arguments[i] = carved_file.clone();\n        }\n    }\n\n    info!(\"Spawning process {} {:?}\", command, extractor.arguments);\n    match process::Command::new(&command)\n        .args(&extractor.arguments)\n        .stdout(process::Stdio::null())\n        .stderr(process::Stdio::null())\n        .current_dir(output_directory)\n        .spawn()\n    {\n        Err(e) => {\n            error!(\n                \"Failed to execute command {}{:?}: {}\",\n                command, extractor.arguments, e\n            );\n            Err(e)\n        }\n\n        Ok(child) => {\n            // If the process was spawned successfully, return some information about the process\n            let proc_info = ProcInfo {\n                child,\n                carved_file: carved_file.clone(),\n                exit_codes: extractor.exit_codes,\n            };\n\n            Ok(proc_info)\n        }\n    }\n}\n\n/// Waits for an extraction process to complete.\n/// Returns ExtractionError if the extractor was prematurely terminated, else returns an ExtractionResult.\nfn proc_wait(mut worker_info: ProcInfo) -> Result<ExtractionResult, ExtractionError> {\n    // The standard exit success value is 0\n    const EXIT_SUCCESS: i32 = 0;\n\n    // Block until child process has terminated\n    match worker_info.child.wait() {\n        // Child was terminated from an external signal, status unknown, assume failure but do nothing else\n        Err(e) => {\n            error!(\"Failed to retreive child process status: {e}\");\n            Err(ExtractionError)\n        }\n\n        // Child terminated with an exit status\n        Ok(status) => {\n            // Assume failure until proven otherwise\n            let mut extraction_success: bool = false;\n\n            // Clean up the carved file used as input to the extractor\n            debug!(\"Deleting carved file {}\", worker_info.carved_file);\n            if let Err(e) = fs::remove_file(worker_info.carved_file.clone()) {\n                warn!(\n                    \"Failed to remove carved file '{}': {}\",\n                    worker_info.carved_file, e\n                );\n            };\n\n            // Check the extractor's exit status\n            match status.code() {\n                None => {\n                    extraction_success = false;\n                }\n\n                Some(code) => {\n                    // Make sure the extractor's exit code is an expected one\n                    if code == EXIT_SUCCESS || worker_info.exit_codes.contains(&code) {\n                        extraction_success = true;\n                    } else {\n                        warn!(\"Child process exited with unexpected code: {code}\");\n                    }\n                }\n            }\n\n            // Return an ExtractionResult with the appropriate success status\n            Ok(ExtractionResult {\n                success: extraction_success,\n                ..Default::default()\n            })\n        }\n    }\n}\n\n// Create an output directory in which to place extraction results\nfn create_output_directory(file_path: &str, offset: usize) -> Result<String, std::io::Error> {\n    let chroot = Chroot::new(None);\n\n    // Output directory will be: <file_path.extracted/<hex offset>\n    let output_directory = format!(\n        \"{}.extracted{}{:X}\",\n        file_path,\n        path::MAIN_SEPARATOR,\n        offset\n    );\n\n    // First, remove the output directory if it exists from a previous run\n    if !chroot.remove_directory(&output_directory) {\n        return Err(std::io::Error::other(\"Directory deletion failed\"));\n    }\n\n    // Create the output directory, equivalent of mkdir -p\n    if !chroot.create_directory(&output_directory) {\n        return Err(std::io::Error::other(\"Directory creation failed\"));\n    }\n\n    Ok(output_directory)\n}\n\n/// Returns true if the size of the provided extractor output directory is greater than zero.\n/// Note that any intermediate/carved files must be deleted *before* calling this function.\nfn was_something_extracted(output_directory: &str) -> bool {\n    let output_directory_path = path::Path::new(output_directory);\n    debug!(\"Checking output directory {output_directory} for results\");\n\n    // Walk the output directory looking for something, anything, that isn't an empty file\n    for entry in WalkDir::new(output_directory).into_iter() {\n        match entry {\n            Err(e) => {\n                warn!(\"Failed to retrieve output directory entry: {e}\");\n                continue;\n            }\n            Ok(entry) => {\n                // Don't include the base output directory path itself\n                if entry.path() == output_directory_path {\n                    continue;\n                }\n\n                debug!(\"Found output file {}\", entry.path().display());\n\n                match fs::symlink_metadata(entry.path()) {\n                    Err(_e) => continue,\n                    Ok(md) => {\n                        if md.len() > 0 {\n                            return true;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    false\n}\n"
  },
  {
    "path": "src/extractors/csman.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::csman::{CSManEntry, parse_csman_entry, parse_csman_header};\nuse miniz_oxide::inflate;\nuse std::collections::HashMap;\n\n/// Defines the internal extractor function for CSMan DAT files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::csman::csman_extractor;\n///\n/// match csman_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn csman_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_csman_dat),\n        ..Default::default()\n    }\n}\n\n/// Validate and extract CSMan DAT file entries\npub fn extract_csman_dat(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const COMPRESSED_HEADER_SIZE: usize = 2;\n\n    // Return value\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    let mut csman_entries: Vec<CSManEntry> = Vec::new();\n\n    // Parse the CSMAN header\n    if let Ok(csman_header) = parse_csman_header(&file_data[offset..]) {\n        // Calulate the start and end offsets of the CSMAN entries\n        let entries_start: usize = offset + csman_header.header_size;\n        let entries_end: usize = entries_start + csman_header.data_size;\n\n        // Get the CSMAN entry data\n        if let Some(raw_entry_data) = file_data.get(entries_start..entries_end) {\n            let mut entry_data = raw_entry_data.to_vec();\n\n            // If the entries are compressed, decompress it (zlib compression)\n            if csman_header.compressed {\n                if let Some(compressed_data) = raw_entry_data.get(COMPRESSED_HEADER_SIZE..) {\n                    match inflate::decompress_to_vec(compressed_data) {\n                        Err(_) => {\n                            return result;\n                        }\n                        Ok(decompressed_data) => {\n                            entry_data = decompressed_data.clone();\n                        }\n                    }\n                }\n            }\n\n            // Offsets for processing CSMAN entries in entry_data\n            let mut next_offset: usize = 0;\n            let mut previous_offset = None;\n            let available_data: usize = entry_data.len();\n\n            // Loop while there is still data that can be safely parsed\n            while is_offset_safe(available_data, next_offset, previous_offset) {\n                // Get the next entry's data\n                match entry_data.get(next_offset..) {\n                    None => {\n                        break;\n                    }\n                    Some(next_entry_data) => {\n                        // Parse the next entry\n                        match parse_csman_entry(next_entry_data, &csman_header.endianness) {\n                            Err(_) => {\n                                break;\n                            }\n                            Ok(entry) => {\n                                if entry.eof {\n                                    // Last entry should be an EOF marker; an EOF marker should always exist.\n                                    // There should be at least one valid entry.\n                                    result.success = !csman_entries.is_empty();\n                                    break;\n                                } else {\n                                    // Append this entry to the list of entries and update the offsets to process the next entry\n                                    csman_entries.push(entry.clone());\n                                    previous_offset = Some(next_offset);\n                                    next_offset += entry.size;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            // If all entries were processed successfully\n            if result.success {\n                // Update the reported size of data processed\n                result.size = Some(csman_header.header_size + csman_header.data_size);\n\n                // If extraction was requested, extract each entry using the entry key as the file name\n                if output_directory.is_some() {\n                    // All files will be written inside the provided output directory\n                    let chroot = Chroot::new(output_directory);\n\n                    // There may be more than one entry with the same key; track the key and how many times it was encountered\n                    let mut processed_entries: HashMap<usize, usize> = HashMap::new();\n\n                    // Loop through all entries\n                    for entry in csman_entries {\n                        // File name is [key value, in ASCII hex].dat\n                        let mut file_name = format!(\"{:08X}.dat\", entry.key);\n\n                        // If this key value has already been extracted, file name is [key value, in ASCII hex].dat_[count]\n                        if processed_entries.contains_key(&entry.key) {\n                            file_name = format!(\"{}_{}\", file_name, processed_entries[&entry.key]);\n                            processed_entries.insert(entry.key, processed_entries[&entry.key] + 1);\n                        } else {\n                            processed_entries.insert(entry.key, 1);\n                        }\n\n                        if !chroot.create_file(&file_name, &entry.value) {\n                            result.success = false;\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/dahua_zip.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::signatures::zip::find_zip_eof;\n\n/// Defines the internal extractor function for carving Dahua ZIP files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::dahua_zip::dahua_zip_extractor;\n///\n/// match dahua_zip_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn dahua_zip_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_dahua_zip),\n        ..Default::default()\n    }\n}\n\n/// Carves out a Dahua ZIP file and converts it to a normal ZIP file\npub fn extract_dahua_zip(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"dahua.zip\";\n    const ZIP_HEADER: &[u8] = b\"PK\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Locate the end of the zip archive\n    if let Ok(zip_info) = find_zip_eof(file_data, offset) {\n        // Calculate total size of the zip archive, report success\n        result.size = Some(zip_info.eof - offset);\n        result.success = true;\n\n        // If extraction was requested, carve the zip archive to disk, replacing the Dahua ZIP magic bytes\n        // with the standard ZIP magic bytes.\n        if output_directory.is_some() {\n            // Start and end offsets of the data to carve\n            let start_data = offset + ZIP_HEADER.len();\n            let end_data = offset + result.size.unwrap();\n\n            let chroot = Chroot::new(output_directory);\n\n            // Get the data to carve\n            match file_data.get(start_data..end_data) {\n                None => {\n                    result.success = false;\n                }\n                Some(zip_data) => {\n                    // First write the normal ZIP header magic bytes to disk\n                    if !chroot.create_file(OUTFILE_NAME, ZIP_HEADER) {\n                        result.success = false;\n                    } else {\n                        // Append the rest of the ZIP archive to disk\n                        result.success = chroot.append_to_file(OUTFILE_NAME, zip_data);\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/dmg.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the dmg2img utility to convert DMG images to MBR\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::dmg::dmg_extractor;\n///\n/// match dmg_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn dmg_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"dmg2img\".to_string()),\n        extension: \"dmg\".to_string(),\n        arguments: vec![\n            \"-i\".to_string(), // Input file\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n            \"-o\".to_string(), // Output file\n            \"mbr.img\".to_string(),\n        ],\n        exit_codes: vec![0, 1],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/dtb.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::dtb::{parse_dtb_header, parse_dtb_node};\nuse log::error;\n\n/// Defines the internal extractor function for extracting Device Tree Blobs\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::dtb::dtb_extractor;\n///\n/// match dtb_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn dtb_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_dtb),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for extracting Device Tree Blobs\npub fn extract_dtb(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    let mut heirerarchy: Vec<String> = Vec::new();\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the DTB file header\n    if let Ok(dtb_header) = parse_dtb_header(&file_data[offset..]) {\n        // Get all the DTB data\n        if let Some(dtb_data) = file_data.get(offset..offset + dtb_header.total_size) {\n            // DTB node entries start at the structure offset specified in the DTB header\n            let mut entry_offset = dtb_header.struct_offset;\n            let mut previous_entry_offset = None;\n            let available_data = dtb_data.len();\n\n            // Loop over all DTB node entries\n            while is_offset_safe(available_data, entry_offset, previous_entry_offset) {\n                // Parse the next DTB node entry\n                let node = parse_dtb_node(&dtb_header, dtb_data, entry_offset);\n\n                // Beginning of a node, add it to the heirerarchy list\n                if node.begin {\n                    if !node.name.is_empty() {\n                        heirerarchy.push(node.name.clone());\n                    }\n                // End of a node, remove it from the heirerarchy list\n                } else if node.end {\n                    if !heirerarchy.is_empty() {\n                        heirerarchy.pop();\n                    }\n                // End of the DTB structure, return success only if the whole DTB structure was parsed successfully up to the EOF marker\n                } else if node.eof {\n                    result.success = true;\n                    result.size = Some(available_data);\n                    break;\n                // DTB property, extract it to disk\n                } else if node.property {\n                    if output_directory.is_some() {\n                        let chroot = Chroot::new(output_directory);\n                        let dir_path = heirerarchy.join(std::path::MAIN_SEPARATOR_STR);\n                        let file_path = chroot.safe_path_join(&dir_path, &node.name);\n\n                        if !chroot.create_directory(dir_path) {\n                            break;\n                        }\n\n                        if !chroot.create_file(file_path, &node.data) {\n                            break;\n                        }\n                    }\n                // The only other supported node type is NOP\n                } else if !node.nop {\n                    error!(\"Unknown or invalid DTB node\");\n                    break;\n                }\n\n                // Update offsets to parse the next DTB structure entry\n                previous_entry_offset = Some(entry_offset);\n                entry_offset += node.total_size;\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/dumpifs.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the dumpifs utility to extract QNX IFS images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::dumpifs::dumpifs_extractor;\n///\n/// match dumpifs_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn dumpifs_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"dumpifs\".to_string()),\n        extension: \"ifs\".to_string(),\n        arguments: vec![\n            \"-x\".to_string(), // Extract the image\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/dxbc.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::dxbc::parse_dxbc_header;\n\n/// Defines the internal extractor function for carving out DXBC images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::dxbc::dxbc_extractor;\n///\n/// match dxbc_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn dxbc_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_dxbc_file),\n        ..Default::default()\n    }\n}\n\npub fn extract_dxbc_file(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"shader.dxbc\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Ok(header) = parse_dxbc_header(&file_data[offset..]) {\n        // Report success\n        result.size = Some(header.size);\n        result.success = true;\n\n        // Do extraction, if requested\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n            result.success =\n                chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/encfw.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\n\n/// Defines the internal extractor function for decrypting known encrypted firmware\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::encfw::encfw_extractor;\n///\n/// match encfw_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn encfw_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(encfw_decrypt),\n        ..Default::default()\n    }\n}\n\n/// Attempts to decrypt known encrypted firmware images\npub fn encfw_decrypt(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTPUT_FILE_NAME: &str = \"decrypted.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Ok(decrypted_data) = delink::decrypt(&file_data[offset..]) {\n        result.success = true;\n\n        // Write to file, if requested\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n            result.success = chroot.create_file(OUTPUT_FILE_NAME, &decrypted_data);\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/gif.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::common::StructureError;\nuse crate::structures::gif::{parse_gif_extension, parse_gif_header, parse_gif_image_descriptor};\n\n/// Defines the internal extractor function for carving out GIF images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::gif::gif_extractor;\n///\n/// match gif_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn gif_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_gif_image),\n        ..Default::default()\n    }\n}\n\n/// Parses and carves a GIF image from a file\npub fn extract_gif_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"image.gif\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the GIF header\n    if let Ok(gif_header) = parse_gif_header(&file_data[offset..]) {\n        // GIF data follows the gif header\n        if let Some(gif_image_data) = file_data.get(offset + gif_header.size..) {\n            // Determine the size of the GIF image data\n            if let Some(gif_data_size) = get_gif_data_size(gif_image_data) {\n                // Report success\n                result.size = Some(gif_header.size + gif_data_size);\n                result.success = true;\n\n                // Do extraction, if requested\n                if output_directory.is_some() {\n                    let chroot = Chroot::new(output_directory);\n                    result.success =\n                        chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());\n                }\n            }\n        }\n    }\n\n    result\n}\n\n/// Returns the size of the GIF data that follows the GIF header\nfn get_gif_data_size(gif_data: &[u8]) -> Option<usize> {\n    // GIF block types\n    const EXTENSION: u8 = 0x21;\n    const TERMINATOR: u8 = 0x3B;\n    const IMAGE_DESCRIPTOR: u8 = 0x2C;\n\n    let mut next_offset: usize = 0;\n    let mut previous_offset = None;\n    let available_data = gif_data.len();\n\n    // Loop through all GIF data blocks\n    while is_offset_safe(available_data, next_offset, previous_offset) {\n        let block_size: Result<usize, StructureError>;\n\n        // Get the block type of the next block\n        match gif_data.get(next_offset) {\n            None => break,\n            Some(block_type) => {\n                // Parse the block type accordingly\n                if *block_type == IMAGE_DESCRIPTOR {\n                    block_size = parse_gif_image_descriptor(&gif_data[next_offset..]);\n                } else if *block_type == EXTENSION {\n                    block_size = parse_gif_extension(&gif_data[next_offset..]);\n                } else if *block_type == TERMINATOR {\n                    // Only return the GIF size if we've found a termination block.\n                    // The +1 is for the size of the block_type u8.\n                    return Some(next_offset + 1);\n                } else {\n                    break;\n                }\n            }\n        }\n\n        // Check if the block was parsed successfully\n        match block_size {\n            Err(_) => break,\n            Ok(this_block_size) => {\n                // Everything looks OK, go to the next block\n                previous_offset = Some(next_offset);\n                next_offset += this_block_size;\n            }\n        }\n    }\n\n    // Something went wrong, failure\n    None\n}\n"
  },
  {
    "path": "src/extractors/gpg.rs",
    "content": "use crate::extractors::common::{ExtractionResult, Extractor, ExtractorType};\nuse crate::extractors::inflate;\n\n/// Defines the internal extractor function for decompressing signed GPG data\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::gpg::gpg_extractor;\n///\n/// match gpg_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn gpg_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(gpg_decompress),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for decompressing signed GPG data\npub fn gpg_decompress(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Size of the GPG header\n    const HEADER_SIZE: usize = 2;\n\n    let mut exresult = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Do the decompression, ignoring the GPG header\n    let inflate_result =\n        inflate::inflate_decompressor(file_data, offset + HEADER_SIZE, output_directory);\n\n    // Check that the data decompressed OK\n    if inflate_result.success {\n        exresult.success = true;\n        exresult.size = Some(HEADER_SIZE + inflate_result.size);\n    }\n\n    exresult\n}\n"
  },
  {
    "path": "src/extractors/gzip.rs",
    "content": "use crate::extractors::common::{ExtractionResult, Extractor, ExtractorType};\nuse crate::extractors::inflate;\nuse crate::structures::gzip::parse_gzip_header;\n\n/// Defines the internal extractor function for decompressing gzip data\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::gzip::gzip_extractor;\n///\n/// match gzip_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn gzip_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(gzip_decompress),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for gzip compressed data\npub fn gzip_decompress(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    let mut exresult = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the gzip header\n    if let Ok(gzip_header) = parse_gzip_header(&file_data[offset..]) {\n        // Deflate compressed data starts at the end of the gzip header\n        let deflate_data_start: usize = offset + gzip_header.size;\n\n        if file_data.len() > deflate_data_start {\n            let inflate_result =\n                inflate::inflate_decompressor(file_data, deflate_data_start, output_directory);\n            if inflate_result.success {\n                exresult.success = true;\n                exresult.size = Some(inflate_result.size);\n            }\n        }\n    }\n\n    exresult\n}\n"
  },
  {
    "path": "src/extractors/inflate.rs",
    "content": "use crate::extractors::common::Chroot;\nuse adler32::RollingAdler32;\nuse flate2::bufread::DeflateDecoder;\nuse std::io::Read;\n\n#[derive(Debug, Default, Clone)]\npub struct DeflateResult {\n    pub size: usize,\n    pub adler32: u32,\n    pub success: bool,\n}\n\n/// Decompressor for inflating deflated data.\n/// For internal use, does not conform to the standard extractor format.\npub fn inflate_decompressor(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> DeflateResult {\n    // Size of decompression buffer\n    const BLOCK_SIZE: usize = 8192;\n    // Output file for decompressed data\n    const OUTPUT_FILE_NAME: &str = \"decompressed.bin\";\n\n    let mut result = DeflateResult {\n        ..Default::default()\n    };\n\n    let mut adler32_checksum = RollingAdler32::new();\n    let mut decompressed_buffer = [0; BLOCK_SIZE];\n    let mut decompressor = DeflateDecoder::new(&file_data[offset..]);\n\n    /*\n     * Loop through all compressed data and decompress it.\n     *\n     * This has a significant performance hit since 1) decompression takes time, and 2) data is\n     * decompressed once during signature validation and a second time during extraction (if extraction\n     * was requested).\n     *\n     * The advantage is that not only are we 100% sure that this data is a valid deflate stream, but we\n     * can also determine the exact size of the deflated data.\n     */\n    loop {\n        // Decompress a block of data\n        match decompressor.read(&mut decompressed_buffer) {\n            Err(_) => {\n                // Break on decompression error\n                break;\n            }\n            Ok(n) => {\n                // Decompressed a block of data, update checksum and if extraction was requested write the decompressed block to the output file\n                if n > 0 {\n                    adler32_checksum.update_buffer(&decompressed_buffer[0..n]);\n\n                    if output_directory.is_some() {\n                        let chroot = Chroot::new(output_directory);\n                        if !chroot.append_to_file(OUTPUT_FILE_NAME, &decompressed_buffer[0..n]) {\n                            // If writing data to file fails, break\n                            break;\n                        }\n                    }\n                }\n\n                // No data was read, end of compression stream\n                if n == 0 {\n                    // If some data was actually decompressed, report success and the number of input bytes consumed\n                    if decompressor.total_out() > 0 {\n                        result.success = true;\n                        result.adler32 = adler32_checksum.hash();\n                        result.size = decompressor.total_in() as usize;\n                    }\n\n                    // Nothing else to do, break\n                    break;\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/iso9660.rs",
    "content": "use crate::extractors;\nuse crate::extractors::sevenzip::sevenzip_extractor;\n\n/// Describes how to run the 7z utility to extract ISO images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::iso9660::iso9660_extractor;\n///\n/// match iso9660_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn iso9660_extractor() -> extractors::common::Extractor {\n    // Same as the normal 7z extractor, but give the carved file an ISO file extension.\n    // The file extension matters, and 7z doesn't handle some ISO sub-formats correctly if the file extension is not '.iso'.\n    let mut extractor = sevenzip_extractor();\n    extractor.extension = \"iso\".to_string();\n    extractor\n}\n"
  },
  {
    "path": "src/extractors/jboot.rs",
    "content": "use crate::common::crc32;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::jboot::parse_jboot_sch2_header;\n\n/// Defines the internal extractor function for carving out JBOOT SCH2 kernels\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::jboot::sch2_extractor;\n///\n/// match sch2_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn sch2_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_jboot_sch2_kernel),\n        ..Default::default()\n    }\n}\n\n/// Extract the kernel described by a JBOOT SCH2 header\npub fn extract_jboot_sch2_kernel(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Output file name\n    const OUTFILE_NAME: &str = \"kernel.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Get the SCH2 data\n    if let Some(sch2_header_data) = file_data.get(offset..) {\n        // Parse the SCH2 header\n        if let Ok(sch2_header) = parse_jboot_sch2_header(sch2_header_data) {\n            let kernel_start: usize = offset + sch2_header.header_size;\n            let kernel_end: usize = kernel_start + sch2_header.kernel_size;\n\n            // Validate the kernel data checksum\n            if let Some(kernel_data) = file_data.get(kernel_start..kernel_end) {\n                if crc32(kernel_data) == (sch2_header.kernel_checksum as u32) {\n                    // Everything checks out ok\n                    result.size = Some(sch2_header.header_size + sch2_header.kernel_size);\n                    result.success = true;\n\n                    if output_directory.is_some() {\n                        let chroot = Chroot::new(output_directory);\n                        result.success = chroot.carve_file(\n                            OUTFILE_NAME,\n                            file_data,\n                            kernel_start,\n                            sch2_header.kernel_size,\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/jffs2.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the jefferson utility to extract JFFS file systems\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::jffs2::jffs2_extractor;\n///\n/// match jffs2_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn jffs2_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"jefferson\".to_string()),\n        extension: \"img\".to_string(),\n        arguments: vec![\n            \"-f\".to_string(), // Force overwrite if output file, for some reason, exists\n            \"-d\".to_string(), // Output to jffs2-root directory\n            \"jffs2-root\".to_string(),\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0, 1, 2],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/jpeg.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\n\n/// Defines the internal extractor function for carving out JPEG images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::jpeg::jpeg_extractor;\n///\n/// match jpeg_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn jpeg_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_jpeg_image),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for carving JPEG images to disk\npub fn extract_jpeg_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"image.jpg\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Find the JPEG EOF to identify the total JPEG size\n    if let Some(jpeg_data_size) = get_jpeg_data_size(&file_data[offset..]) {\n        result.size = Some(jpeg_data_size);\n        result.success = true;\n\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n            result.success =\n                chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());\n        }\n    }\n\n    result\n}\n\n/// Parses JPEG markers until the EOF marker is found\nfn get_jpeg_data_size(jpeg_data: &[u8]) -> Option<usize> {\n    const SIZE_FIELD_LENGTH: usize = 2;\n    const SOS_SCAN_AHEAD_LENGTH: usize = 2;\n    const MARKER_MAGIC: u8 = 0xFF;\n    const SOS_MARKER: u8 = 0xDA;\n    const EOF_MARKER: u8 = 0xD9;\n\n    let mut next_marker_offset: usize = 0;\n\n    // Most JPEG markers include a size field; these do not\n    let no_length_markers: Vec<u8> = vec![\n        0x00, 0x01, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, EOF_MARKER,\n    ];\n\n    // In a Start Of Scan block, ignore 0xFF marker magics that are followed by one of these bytes\n    let sos_skip_markers: Vec<u8> = vec![0x00, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7];\n\n    loop {\n        // Read the marker magic byte\n        match jpeg_data.get(next_marker_offset) {\n            None => {\n                break;\n            }\n            Some(marker_magic) => {\n                // Make sure this is the correct marker magic\n                if *marker_magic != MARKER_MAGIC {\n                    break;\n                }\n\n                // Include marker magic byte in side of the marker\n                next_marker_offset += 1;\n\n                // Read the marker ID byte\n                match jpeg_data.get(next_marker_offset) {\n                    None => {\n                        break;\n                    }\n                    Some(marker_id) => {\n                        // Include marker ID byte in the size of the marker\n                        next_marker_offset += 1;\n\n                        // Most markers have a 2-byte length field after the marker, stored in big-endian\n                        if !no_length_markers.contains(marker_id) {\n                            match jpeg_data\n                                .get(next_marker_offset..next_marker_offset + SIZE_FIELD_LENGTH)\n                            {\n                                None => {\n                                    break;\n                                }\n                                Some(size_bytes) => {\n                                    next_marker_offset +=\n                                        u16::from_be_bytes(size_bytes.try_into().unwrap()) as usize;\n                                }\n                            }\n                        }\n\n                        // Start Of Scan markers have a size field, but are immediately followed by data not included int\n                        // the size field. Need to scan all the bytes until the next valid JPEG marker is found.\n                        if *marker_id == SOS_MARKER {\n                            loop {\n                                // Get the next two bytes\n                                match jpeg_data.get(\n                                    next_marker_offset..next_marker_offset + SOS_SCAN_AHEAD_LENGTH,\n                                ) {\n                                    None => {\n                                        break;\n                                    }\n                                    Some(next_bytes) => {\n                                        // Check if the next byte is a marker magic byte, *and* that it is not followed by a marker escape byte\n                                        if next_bytes[0] == MARKER_MAGIC\n                                            && !sos_skip_markers.contains(&next_bytes[1])\n                                        {\n                                            break;\n                                        } else {\n                                            // Go to the next byte\n                                            next_marker_offset += 1;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        // EOF marker indicates the end of the JPEG image\n                        if *marker_id == EOF_MARKER {\n                            return Some(next_marker_offset);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/extractors/linux.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the vmlinux-to-elf utility to convert raw kernel images to ELF files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::linux::linux_kernel_extractor;\n///\n/// match linux_kernel_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn linux_kernel_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        do_not_recurse: true,\n        utility: extractors::common::ExtractorType::External(\"vmlinux-to-elf\".to_string()),\n        extension: \"bin\".to_string(),\n        arguments: vec![\n            // Input file\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n            // Output file\n            \"linux_kernel.elf\".to_string(),\n        ],\n        exit_codes: vec![0],\n    }\n}\n"
  },
  {
    "path": "src/extractors/lz4.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the lz4 utility to extract LZ4 compressed files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::lz4::lz4_extractor;\n///\n/// match lz4_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn lz4_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"lz4\".to_string()),\n        extension: \"lz4\".to_string(),\n        arguments: vec![\n            \"-f\".to_string(), // Force overwirte if, for some reason, the output file exists\n            \"-d\".to_string(), // Perform a decompression\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n            \"decompressed.bin\".to_string(), // Output file\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/lzfse.rs",
    "content": "use crate::extractors::common::{Extractor, ExtractorType, SOURCE_FILE_PLACEHOLDER};\n\n/// Describes how to run the lzfse utility to decompress LZFSE files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::lzfse::lzfse_extractor;\n///\n/// match lzfse_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn lzfse_extractor() -> Extractor {\n    const OUTPUT_FILE_NAME: &str = \"decompressed.bin\";\n\n    Extractor {\n        utility: ExtractorType::External(\"lzfse\".to_string()),\n        extension: \"bin\".to_string(),\n        arguments: vec![\n            \"-decode\".to_string(), // Do decompression\n            \"-i\".to_string(),      // Input file\n            SOURCE_FILE_PLACEHOLDER.to_string(),\n            \"-o\".to_string(), // Output file\n            OUTPUT_FILE_NAME.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/lzma.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse liblzma::stream::{Action, Status, Stream};\n\n/// Defines the internal extractor function for decompressing LZMA/XZ data\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::lzma::lzma_extractor;\n///\n/// match lzma_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn lzma_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(lzma_decompress),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for decompressing LZMA/XZ data streams\npub fn lzma_decompress(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Output file name\n    const OUTPUT_FILE_NAME: &str = \"decompressed.bin\";\n    // Output buffer size\n    const BLOCK_SIZE: usize = 8192;\n    // Maximum memory limit: 4GB\n    const MEM_LIMIT: u64 = 4 * 1024 * 1024 * 1024;\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Output buffer\n    let mut output_buf = [0; BLOCK_SIZE];\n\n    // Input compression stream\n    let lzma_stream = &file_data[offset..];\n\n    // Instantiate a new decoder, auto-detect LZMA or XZ\n    if let Ok(mut decompressor) = Stream::new_auto_decoder(MEM_LIMIT, 0) {\n        // Tracks number of bytes written to disk\n        let mut bytes_written: usize = 0;\n        // Tracks current position of bytes consumed from input stream\n        let mut stream_position: usize = 0;\n\n        /*\n         * Loop through all compressed data and decompress it.\n         *\n         * This has a significant performance hit since 1) decompression takes time, and 2) data is\n         * decompressed once during signature validation and a second time during extraction (if extraction\n         * was requested).\n         *\n         * The advantage is that not only are we 100% sure that this data is a valid LZMA stream, but we\n         * can also determine the exact size of the LZMA data.\n         */\n        loop {\n            // Decompress data into output_buf\n            match decompressor.process(\n                &lzma_stream[stream_position..],\n                &mut output_buf,\n                Action::Run,\n            ) {\n                Err(_) => {\n                    // Decompression error, break\n                    break;\n                }\n                Ok(status) => {\n                    // Check reported status\n                    match status {\n                        Status::GetCheck => break,\n                        Status::MemNeeded => break,\n                        Status::Ok => {\n                            // Decompression OK, but there is still more data to decompress\n                            stream_position = decompressor.total_in() as usize;\n                        }\n                        Status::StreamEnd => {\n                            // Decompression complete. If some data was decompressed, report success, else break.\n                            if decompressor.total_out() > 0 {\n                                result.success = true;\n                                result.size = Some(decompressor.total_in() as usize);\n                            } else {\n                                break;\n                            }\n                        }\n                    }\n\n                    // Some data was decompressed successfully; if extraction was requested, write the data to disk.\n                    if output_directory.is_some() {\n                        // Number of decompressed bytes in the output buffer\n                        let n = (decompressor.total_out() as usize) - bytes_written;\n\n                        let chroot = Chroot::new(output_directory);\n                        if !chroot.append_to_file(OUTPUT_FILE_NAME, &output_buf[0..n]) {\n                            // If writing data to disk fails, report failure and break\n                            result.success = false;\n                            break;\n                        }\n\n                        // Remember how much data has been written to disk\n                        bytes_written += n;\n                    }\n\n                    // If result.success is true, then everything has been processed and written to disk successfully.\n                    if result.success {\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/lzop.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the lzop utility to extract LZO compressed files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::lzop::lzop_extractor;\n///\n/// match lzop_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn lzop_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"lzop\".to_string()),\n        extension: \"lzo\".to_string(),\n        arguments: vec![\n            \"-p\".to_string(), // Output to the current directory\n            \"-N\".to_string(), // Restore original file name\n            \"-d\".to_string(), // Perform a decompression\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/matter_ota.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::matter_ota::parse_matter_ota_header;\n\n/// Defines the internal extractor function for extracting a Matter OTA firmware payload */\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::matter_ota::matter_ota_extractor;\n///\n/// match matter_ota_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn matter_ota_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_matter_ota),\n        ..Default::default()\n    }\n}\n\n/// Matter OTA firmware payload extractor\npub fn extract_matter_ota(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"matter_payload.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Ok(ota_header) = parse_matter_ota_header(&file_data[offset..]) {\n        const MAGIC_SIZE: usize = 4;\n        const TOTAL_SIZE_SIZE: usize = 8;\n        const HEADER_SIZE_SIZE: usize = 4;\n\n        let total_header_size =\n            MAGIC_SIZE + TOTAL_SIZE_SIZE + HEADER_SIZE_SIZE + ota_header.header_size;\n\n        result.success = true;\n        result.size = Some(ota_header.total_size);\n\n        let payload_start = offset + total_header_size;\n        let payload_end = offset + total_header_size + ota_header.payload_size;\n\n        // Sanity check reported payload size and get the payload data\n        if let Some(payload_data) = file_data.get(payload_start..payload_end) {\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n                result.success =\n                    chroot.carve_file(OUTFILE_NAME, payload_data, 0, payload_data.len());\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/mbr.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::mbr::parse_mbr_image;\n\n/// Defines the internal extractor function for MBR partitions\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::mbr::mbr_extractor;\n///\n/// match mbr_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn mbr_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_mbr_partitions),\n        ..Default::default()\n    }\n}\n\n/// Validate and extract partitions from an MBR\npub fn extract_mbr_partitions(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Return value\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    let available_data = file_data.len() - offset;\n\n    // Parse the MBR header\n    if let Ok(mbr_header) = parse_mbr_image(&file_data[offset..]) {\n        // Make sure there is at least one valid partition\n        if !mbr_header.partitions.is_empty() {\n            // Make sure the reported size of the MBR does not extend beyond EOF\n            if available_data >= mbr_header.image_size {\n                // Everything looks ok\n                result.success = true;\n                result.size = Some(mbr_header.image_size);\n\n                // Do extraction if requested\n                if output_directory.is_some() {\n                    // Chroot extracted files into the output directory\n                    let chroot = Chroot::new(output_directory);\n\n                    // Loop through each partition\n                    for (partition_count, partition) in mbr_header.partitions.iter().enumerate() {\n                        // Partition names are not unique, output file will be: \"<name>_partition.<partition count>\"\n                        let partition_name =\n                            format!(\"{}_partition.{}\", partition.name, partition_count);\n\n                        // Carve out the partition\n                        result.success = chroot.carve_file(\n                            partition_name,\n                            file_data,\n                            partition.start,\n                            partition.size,\n                        );\n\n                        // If partition extraction failed, quit and report a failure\n                        if !result.success {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/mh01.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::mh01::parse_mh01_header;\n\n/// Defines the internal extractor function for carving out MH01 firmware images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::mh01::mh01_extractor;\n///\n/// match mh01_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn mh01_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_mh01_image),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for carve pieces of MH01 images to disk\npub fn extract_mh01_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // File names for the three portions of the MH01 firmware image\n    const IV_FILE_NAME: &str = \"iv.bin\";\n    const SIGNATURE_FILE_NAME: &str = \"signature.bin\";\n    const ENCRYPTED_DATA_FILE_NAME: &str = \"encrypted.bin\";\n    const DECRYPTED_DATA_FILE_NAME: &str = \"decrypted.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Get the MH01 image data\n    if let Some(mh01_data) = file_data.get(offset..) {\n        // Parse the MH01 header\n        if let Ok(mh01_header) = parse_mh01_header(mh01_data) {\n            result.size = Some(mh01_header.total_size);\n\n            // If extraction was requested, do it\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n\n                // Try to decrypt the firmware\n                match delink::mh01::decrypt(mh01_data) {\n                    Ok(decrypted_data) => {\n                        // Write decrypted data to disk\n                        result.success =\n                            chroot.create_file(DECRYPTED_DATA_FILE_NAME, &decrypted_data);\n                    }\n                    Err(_) => {\n                        // Decryption failture; extract each part of the firmware image, ensuring that each one extracts without error\n                        result.success = chroot.carve_file(\n                            IV_FILE_NAME,\n                            mh01_data,\n                            mh01_header.iv_offset,\n                            mh01_header.iv_size,\n                        ) && chroot.carve_file(\n                            SIGNATURE_FILE_NAME,\n                            mh01_data,\n                            mh01_header.signature_offset,\n                            mh01_header.signature_size,\n                        ) && chroot.carve_file(\n                            ENCRYPTED_DATA_FILE_NAME,\n                            mh01_data,\n                            mh01_header.encrypted_data_offset,\n                            mh01_header.encrypted_data_size,\n                        );\n                    }\n                }\n            // No extraction requested, just return success\n            } else {\n                result.success = true;\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/pcap.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::pcap::{parse_pcapng_block, parse_pcapng_section_block};\n\n/// Defines the internal extractor function for extracting pcap-ng files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::pcap::pcapng_extractor;\n///\n/// match pcapng_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn pcapng_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(pcapng_carver),\n        ..Default::default()\n    }\n}\n\n/// Carves a pcap-ng file to disk\npub fn pcapng_carver(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Output file name\n    const OUTPUT_FILE_NAME: &str = \"capture.pcapng\";\n\n    // Pcap-NG files must have at least two blocks: a section header block and an interface description block\n    const MIN_BLOCK_COUNT: usize = 2;\n\n    // Return value\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // All pcap-ng files start with a section header; parse it\n    if let Ok(section_header) = parse_pcapng_section_block(&file_data[offset..]) {\n        let mut block_count: usize = 1;\n        let available_data = file_data.len() - offset;\n        let mut next_offset = offset + section_header.block_size;\n        let mut previous_offset = None;\n\n        // Loop through all blocks in the pcap-ng file\n        while is_offset_safe(available_data, next_offset, previous_offset) {\n            match file_data.get(next_offset..) {\n                None => {\n                    break;\n                }\n                Some(block_data) => {\n                    // Parse the next block header\n                    match parse_pcapng_block(block_data, &section_header.endianness) {\n                        Err(_) => {\n                            break;\n                        }\n                        Ok(block_header) => {\n                            // This block looks valid, go to the next one\n                            block_count += 1;\n                            previous_offset = Some(next_offset);\n                            next_offset += block_header.block_size;\n                        }\n                    }\n                }\n            }\n        }\n\n        // Must have processed the minimum number of blocks\n        if block_count >= MIN_BLOCK_COUNT {\n            // Everything looks OK\n            result.size = Some(next_offset - offset);\n            result.success = true;\n\n            // Do extraction if requested\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n                result.success =\n                    chroot.carve_file(OUTPUT_FILE_NAME, file_data, offset, result.size.unwrap());\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/pem.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse aho_corasick::AhoCorasick;\n\n/// Defines the internal extractor function for carving out PEM keys\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::pem::pem_key_extractor;\n///\n/// match pem_key_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn pem_key_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(pem_key_carver),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor function for carving out PEM certs\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::pem::pem_certificate_extractor;\n///\n/// match pem_certificate_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn pem_certificate_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(pem_certificate_carver),\n        ..Default::default()\n    }\n}\n\npub fn pem_certificate_carver(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const CERTIFICATE_FILE_NAME: &str = \"pem.crt\";\n    pem_carver(\n        file_data,\n        offset,\n        output_directory,\n        Some(CERTIFICATE_FILE_NAME),\n    )\n}\n\npub fn pem_key_carver(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const KEY_FILE_NAME: &str = \"pem.key\";\n    pem_carver(file_data, offset, output_directory, Some(KEY_FILE_NAME))\n}\n\npub fn pem_carver(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n    fname: Option<&str>,\n) -> ExtractionResult {\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Some(pem_size) = get_pem_size(file_data, offset) {\n        result.size = Some(pem_size);\n        result.success = true;\n\n        if let Some(outfile) = fname {\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n                result.success =\n                    chroot.carve_file(outfile, file_data, offset, result.size.unwrap());\n            }\n        }\n    }\n\n    result\n}\n\nfn get_pem_size(file_data: &[u8], start_of_pem_offset: usize) -> Option<usize> {\n    let eof_markers = vec![\n        b\"-----END PUBLIC KEY-----\".to_vec(),\n        b\"-----END CERTIFICATE-----\".to_vec(),\n        b\"-----END PRIVATE KEY-----\".to_vec(),\n        b\"-----END EC PRIVATE KEY-----\".to_vec(),\n        b\"-----END RSA PRIVATE KEY-----\".to_vec(),\n        b\"-----END DSA PRIVATE KEY-----\".to_vec(),\n        b\"-----END OPENSSH PRIVATE KEY-----\".to_vec(),\n    ];\n\n    let newline_chars: Vec<u8> = vec![0x0D, 0x0A];\n\n    let grep = AhoCorasick::new(eof_markers.clone()).unwrap();\n\n    // Find the first end marker\n    if let Some(eof_match) = grep\n        .find_overlapping_iter(&file_data[start_of_pem_offset..])\n        .next()\n    {\n        let eof_marker_index: usize = eof_match.pattern().as_usize();\n        let mut pem_size = eof_match.start() + eof_markers[eof_marker_index].len();\n\n        // Include any trailing newline characters in the total size of the PEM file\n        while (start_of_pem_offset + pem_size) < file_data.len() {\n            if newline_chars.contains(&file_data[start_of_pem_offset + pem_size]) {\n                pem_size += 1;\n            } else {\n                break;\n            }\n        }\n\n        return Some(pem_size);\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/extractors/png.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::png::parse_png_chunk_header;\n\n/// Defines the internal extractor function for carving out PNG images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::png::png_extractor;\n///\n/// match png_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn png_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_png_image),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for carving PNG files to disk\npub fn extract_png_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const PNG_HEADER_LEN: usize = 8;\n    const OUTFILE_NAME: &str = \"image.png\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse all the PNG chunks to determine the size of PNG data; first chunk starts immediately after the 8-byte PNG header\n    if let Some(png_data) = file_data.get(offset + PNG_HEADER_LEN..) {\n        if let Some(png_data_size) = get_png_data_size(png_data) {\n            // Total size is the size of the header plus the size of the data\n            result.size = Some(png_data_size + PNG_HEADER_LEN);\n            result.success = true;\n\n            // If extraction was requested, extract the PNG\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n                result.success =\n                    chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());\n            }\n        }\n    }\n\n    result\n}\n\nfn get_png_data_size(png_chunk_data: &[u8]) -> Option<usize> {\n    let available_data = png_chunk_data.len();\n    let mut png_chunk_offset: usize = 0;\n    let mut previous_png_chunk_offset = None;\n\n    // Loop until we run out of data\n    while is_offset_safe(available_data, png_chunk_offset, previous_png_chunk_offset) {\n        // Parse this PNG chunk header\n        match parse_png_chunk_header(&png_chunk_data[png_chunk_offset..]) {\n            Ok(chunk_header) => {\n                // The next chunk header will start immediately after this chunk\n                previous_png_chunk_offset = Some(png_chunk_offset);\n                png_chunk_offset += chunk_header.total_size;\n\n                // If this was the last chunk, then png_chunk_offset is the total size of the PNG data\n                if chunk_header.is_last_chunk {\n                    return Some(png_chunk_offset);\n                }\n            }\n            Err(_) => break,\n        }\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/extractors/rar.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the unrar utility to extract RAR archives\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::rar::rar_extractor;\n///\n/// match rar_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn rar_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"unrar\".to_string()),\n        extension: \"rar\".to_string(),\n        arguments: vec![\n            \"x\".to_string(),          // Perform extraction\n            \"-y\".to_string(),         // Answer yes to all questions\n            \"-ppassword\".to_string(), // Set the password to  'password' for password protected rar files\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/riff.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::riff::parse_riff_header;\n\n/// Describes the internal RIFF image extactor\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::riff::riff_extractor;\n///\n/// match riff_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn riff_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_riff_image),\n        do_not_recurse: true,\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for carving RIFF files to disk\npub fn extract_riff_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"image.riff\";\n    const WAV_OUTFILE_NAME: &str = \"video.wav\";\n    const WAV_TYPE: &str = \"WAVE\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Ok(riff_header) = parse_riff_header(&file_data[offset..]) {\n        result.size = Some(riff_header.size);\n        result.success = true;\n\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n\n            let file_path: String = if riff_header.chunk_type == WAV_TYPE {\n                WAV_OUTFILE_NAME.to_string()\n            } else {\n                OUTFILE_NAME.to_string()\n            };\n\n            result.success = chroot.carve_file(file_path, file_data, offset, result.size.unwrap());\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/romfs.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{\n    Chroot, ExtractionError, ExtractionResult, Extractor, ExtractorType,\n};\nuse crate::structures::romfs::{parse_romfs_file_entry, parse_romfs_header};\nuse log::warn;\n\n#[derive(Default, Debug, Clone)]\nstruct RomFSEntry {\n    info: usize,\n    size: usize,\n    name: String,\n    offset: usize,\n    file_type: usize,\n    executable: bool,\n    directory: bool,\n    regular: bool,\n    block_device: bool,\n    character_device: bool,\n    fifo: bool,\n    socket: bool,\n    symlink: bool,\n    symlink_target: String,\n    device_major: usize,\n    device_minor: usize,\n    children: Vec<RomFSEntry>,\n}\n\n/// Defines the internal extractor function for extracting RomFS file systems */\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::romfs::romfs_extractor;\n///\n/// match romfs_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn romfs_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_romfs),\n        ..Default::default()\n    }\n}\n\n/// Internal RomFS extractor\npub fn extract_romfs(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the RomFS header\n    if let Ok(romfs_header) = parse_romfs_header(&file_data[offset..]) {\n        // Calculate start and end offsets of RomFS image\n        let romfs_data_start: usize = offset;\n        let romfs_data_end: usize = romfs_data_start + romfs_header.image_size;\n\n        // Sanity check reported image size and get the romfs data\n        if let Some(romfs_data) = file_data.get(romfs_data_start..romfs_data_end) {\n            // Process the RomFS file entries\n            if let Ok(root_entries) = process_romfs_entries(romfs_data, romfs_header.header_size) {\n                // We expect at least one file entry in the root of the RomFS image\n                if !root_entries.is_empty() {\n                    // Everything looks good\n                    result.success = true;\n                    result.size = Some(romfs_header.image_size);\n\n                    // Do extraction, if an output directory was provided\n                    if output_directory.is_some() {\n                        let mut file_count: usize = 0;\n                        let root_parent = \"\".to_string();\n\n                        // RomFS files will be extracted to a sub-directory under the specified\n                        // extraction directory whose name is the RomFS volume name.\n                        let chroot = Chroot::new(output_directory);\n                        let romfs_chroot_dir = chroot.chrooted_path(&romfs_header.volume_name);\n\n                        // Create the romfs output directory, ensuring that it is contained inside the specified extraction directory\n                        if chroot.create_directory(&romfs_chroot_dir) {\n                            // Extract RomFS contents\n                            file_count = extract_romfs_entries(\n                                romfs_data,\n                                &root_entries,\n                                &root_parent,\n                                &romfs_chroot_dir,\n                            );\n                        }\n\n                        // If no files were extracted, extraction was a failure\n                        if file_count == 0 {\n                            result.success = false;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n\n// Recursively processes all RomFS file entries and their children, and returns a list of RomFSEntry structures\nfn process_romfs_entries(\n    romfs_data: &[u8],\n    offset: usize,\n) -> Result<Vec<RomFSEntry>, ExtractionError> {\n    let mut previous_file_offset = None;\n    let mut file_entries: Vec<RomFSEntry> = vec![];\n    let mut processed_entries: Vec<usize> = vec![];\n    let ignore_file_names: Vec<String> = vec![\".\".to_string(), \"..\".to_string()];\n\n    // Total available data\n    let available_data = romfs_data.len();\n\n    // File data starts immediately after the image header; the offset passed in should be the end of the header\n    let mut file_offset: usize = offset;\n\n    /*\n     * Sanity check the available file data against the offset of the next file entry.\n     * The file offset for the next file entry will be 0 when we've reached the end of the entry list.\n     */\n    while file_offset != 0 && is_offset_safe(available_data, file_offset, previous_file_offset) {\n        // Sanity check, no two entries should exist at the same offset, if so, infinite recursion could ensue\n        if processed_entries.contains(&file_offset) {\n            break;\n        } else {\n            processed_entries.push(file_offset);\n        }\n\n        // Parse the next file entry\n        if let Ok(file_header) = parse_romfs_file_entry(&romfs_data[file_offset..]) {\n            // Instantiate a new RomFSEntry structure\n            let mut file_entry = RomFSEntry {\n                ..Default::default()\n            };\n\n            // Populate basic info\n            file_entry.size = file_header.size;\n            file_entry.info = file_header.info;\n            file_entry.name = file_header.name.clone();\n            file_entry.symlink = file_header.symlink;\n            file_entry.regular = file_header.regular;\n            file_entry.directory = file_header.directory;\n            file_entry.file_type = file_header.file_type;\n            file_entry.executable = file_header.executable;\n            file_entry.block_device = file_header.block_device;\n            file_entry.character_device = file_header.character_device;\n            file_entry.fifo = file_header.fifo;\n            file_entry.socket = file_header.socket;\n\n            // Make file_entry.offset an offset relative to the beginning of the RomFS image\n            file_entry.offset = file_offset + file_header.data_offset;\n\n            // Sanity check the file data offset and size fields\n            if (file_entry.offset + file_entry.size) > romfs_data.len() {\n                warn!(\"Invalid offset/size specified for file {}\", file_entry.name);\n                return Err(ExtractionError);\n            }\n\n            // Don't do anything special for '.' or '..' directory entries\n            if !ignore_file_names.contains(&file_entry.name) {\n                // Symlinks need their target paths\n                if file_entry.symlink {\n                    if let Some(symlink_bytes) =\n                        romfs_data.get(file_entry.offset..file_entry.offset + file_entry.size)\n                    {\n                        match String::from_utf8(symlink_bytes.to_vec()) {\n                            Err(e) => {\n                                warn!(\"Failed to convert symlink target path to string: {e}\");\n                                return Err(ExtractionError);\n                            }\n                            Ok(path) => {\n                                file_entry.symlink_target = path.clone();\n                            }\n                        }\n                    } else {\n                        break;\n                    }\n                // Device files have their major/minor numbers encoded into the info field\n                } else if file_entry.block_device || file_entry.character_device {\n                    file_entry.device_minor = file_entry.info & 0xFFFF;\n                    file_entry.device_major = (file_entry.info >> 16) & 0xFFFF;\n                }\n\n                // Directories have children; process them\n                if file_entry.directory {\n                    match process_romfs_entries(romfs_data, file_entry.info) {\n                        Err(e) => return Err(e),\n                        Ok(children) => file_entry.children = children,\n                    }\n                }\n\n                // Only add supported file types to the list of file entries\n                if file_entry.directory || file_entry.symlink || file_entry.regular {\n                    file_entries.push(file_entry);\n                }\n            }\n\n            // The next file header offset is an offset from the beginning of the RomFS image\n            previous_file_offset = Some(file_offset);\n            file_offset = file_header.next_header_offset;\n        } else {\n            // File entry header parsing failed, gtfo\n            break;\n        }\n    }\n\n    Ok(file_entries)\n}\n\n// Recursively extract all RomFS entries, returns the number of extracted files/directories\nfn extract_romfs_entries(\n    romfs_data: &[u8],\n    romfs_files: &Vec<RomFSEntry>,\n    parent_directory: &str,\n    chroot_directory: &str,\n) -> usize {\n    let mut file_count: usize = 0;\n\n    let chroot = Chroot::new(Some(chroot_directory));\n\n    for file_entry in romfs_files {\n        let extraction_success: bool;\n        let file_path = chroot.safe_path_join(parent_directory, &file_entry.name);\n\n        if file_entry.directory {\n            extraction_success = chroot.create_directory(&file_path);\n        } else if file_entry.regular {\n            extraction_success =\n                chroot.carve_file(&file_path, romfs_data, file_entry.offset, file_entry.size);\n        } else if file_entry.symlink {\n            extraction_success = chroot.create_symlink(&file_path, &file_entry.symlink_target);\n        } else if file_entry.fifo {\n            extraction_success = chroot.create_fifo(&file_path);\n        } else if file_entry.socket {\n            extraction_success = chroot.create_socket(&file_path);\n        } else if file_entry.block_device {\n            extraction_success = chroot.create_block_device(\n                &file_path,\n                file_entry.device_major,\n                file_entry.device_minor,\n            );\n        } else if file_entry.character_device {\n            extraction_success = chroot.create_character_device(\n                &file_path,\n                file_entry.device_major,\n                file_entry.device_minor,\n            );\n        } else {\n            continue;\n        }\n\n        if extraction_success {\n            file_count += 1;\n\n            // Extract the children of a directory\n            if file_entry.directory && !file_entry.children.is_empty() {\n                file_count += extract_romfs_entries(\n                    romfs_data,\n                    &file_entry.children,\n                    &file_path,\n                    chroot_directory,\n                );\n            }\n\n            // Make executable files executable\n            if file_entry.regular && file_entry.executable {\n                chroot.make_executable(&file_path);\n            }\n        } else {\n            warn!(\"Failed to extract RomFS file {file_path}\");\n        }\n    }\n\n    // Return the number of files extracted\n    file_count\n}\n"
  },
  {
    "path": "src/extractors/sevenzip.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the 7z utility, supports multiple file formats\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::sevenzip::sevenzip_extractor;\n///\n/// match sevenzip_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn sevenzip_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"7zz\".to_string()),\n        extension: \"bin\".to_string(),\n        arguments: vec![\n            \"x\".to_string(),    // Perform extraction\n            \"-y\".to_string(),   // Assume Yes to all questions\n            \"-o.\".to_string(),  // Output to current working directory\n            \"-p''\".to_string(), // Blank password to prevent hangs if archives are password protected\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        // If there is trailing data after the compressed data, extraction will happen but exit code will be 2\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/squashfs.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the sasquatch utility to extract SquashFS images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::squashfs::squashfs_extractor;\n///\n/// match squashfs_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn squashfs_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"sasquatch\".to_string()),\n        extension: \"sqsh\".to_string(),\n        arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()],\n        // Exit code may be 0 or 2; 2 indicates running as not root, but otherwise extraction is ok\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n\n/// Describes how to run the sasquatch utility to extract little endian SquashFS images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::squashfs::squashfs_le_extractor;\n///\n/// match squashfs_le_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn squashfs_le_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"sasquatch\".to_string()),\n        extension: \"sqsh\".to_string(),\n        arguments: vec![\n            \"-le\".to_string(),\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        // Exit code may be 0 or 2; 2 indicates running as not root, but otherwise extraction is ok\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n\n/// Describes how to run the sasquatch utility to extract big endian SquashFS images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::squashfs::squashfs_be_extractor;\n///\n/// match squashfs_be_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn squashfs_be_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"sasquatch\".to_string()),\n        extension: \"sqsh\".to_string(),\n        arguments: vec![\n            \"-be\".to_string(),\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        // Exit code may be 0 or 2; 2 indicates running as not root, but otherwise extraction is ok\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n\n/// Describes how to run the sasquatch-v4be utility to extract big endian SquashFSv4 images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::squashfs::squashfs_v4_be_extractor;\n///\n/// match squashfs_v4_be_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn squashfs_v4_be_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"sasquatch-v4be\".to_string()),\n        extension: \"sqsh\".to_string(),\n        arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()],\n        // Exit code may be 0 or 2; 2 indicates running as not root, but otherwise extraction is ok\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/srec.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the srec_cat utility to convert Motorola S-records to binary\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::srec::srec_extractor;\n///\n/// match srec_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn srec_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"srec_cat\".to_string()),\n        extension: \"hex\".to_string(),\n        arguments: vec![\n            \"-output\".to_string(),\n            \"s-record.bin\".to_string(),\n            \"-binary\".to_string(),\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/svg.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::svg::parse_svg_image;\n\n/// Defines the internal extractor function for carving out SVG images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::svg::svg_extractor;\n///\n/// match svg_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn svg_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_svg_image),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for carving SVG images to disk\npub fn extract_svg_image(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const OUTFILE_NAME: &str = \"image.svg\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the SVG image to determine its total size\n    if let Ok(svg_image) = parse_svg_image(&file_data[offset..]) {\n        result.size = Some(svg_image.total_size);\n        result.success = true;\n\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n            result.success =\n                chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/swapped.rs",
    "content": "use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\n\n/// Defines the internal extractor function for u16 swapped firmware images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::swapped::swapped_extractor_u16;\n///\n/// match swapped_extractor_u16().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn swapped_extractor_u16() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_swapped_u16),\n        ..Default::default()\n    }\n}\n\n/// Extract firmware where every two bytes have been swapped\npub fn extract_swapped_u16(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const SWAP_BYTE_COUNT: usize = 2;\n    extract_swapped(file_data, offset, output_directory, SWAP_BYTE_COUNT)\n}\n\n/// Extract a block of data where every n bytes have been swapped\nfn extract_swapped(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n    n: usize,\n) -> ExtractionResult {\n    const OUTPUT_FILE_NAME: &str = \"swapped.bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    if let Some(data) = file_data.get(offset..) {\n        let swapped_data = byte_swap(data, n);\n\n        result.success = !swapped_data.is_empty();\n\n        if result.success {\n            result.size = Some(swapped_data.len());\n\n            // Write to file, if requested\n            if output_directory.is_some() {\n                let chroot = Chroot::new(output_directory);\n                result.success = chroot.create_file(OUTPUT_FILE_NAME, &swapped_data);\n            }\n        }\n    }\n\n    result\n}\n\n/// Swap every n bytes of the provided data\n///\n/// ## Example:\n///\n/// ```\n/// use binwalk::extractors::swapped::byte_swap;\n///\n/// assert_eq!(byte_swap(b\"ABCD\", 2), b\"CDAB\");\n/// ```\npub fn byte_swap(data: &[u8], n: usize) -> Vec<u8> {\n    let chunk_size = n * 2;\n    let mut chunker = data.chunks(chunk_size);\n    let mut swapped_data: Vec<u8> = Vec::new();\n\n    loop {\n        match chunker.next() {\n            None => {\n                break;\n            }\n            Some(chunk) => {\n                if chunk.len() != chunk_size {\n                    break;\n                }\n\n                swapped_data.extend(chunk[n..].to_vec());\n                swapped_data.extend(chunk[0..n].to_vec());\n            }\n        }\n    }\n\n    swapped_data\n}\n"
  },
  {
    "path": "src/extractors/tarball.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the tar utility to extract tarball archives\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::tarball::tarball_extractor;\n///\n/// match tarball_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn tarball_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"tar\".to_string()),\n        extension: \"tar\".to_string(),\n        arguments: vec![\n            \"-x\".to_string(),\n            \"-f\".to_string(),\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        // Exit code may be 2 if attempting to create special device files fails\n        exit_codes: vec![0, 2],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/trx.rs",
    "content": "use crate::common::crc32;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::trx::parse_trx_header;\n\n/// Defines the internal TRX extractor\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::trx::trx_extractor;\n///\n/// match trx_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn trx_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_trx_partitions),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for TRX partitions\npub fn extract_trx_partitions(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const CRC_DATA_START_OFFSET: usize = 12;\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Get the TRX data and parse the header\n    if let Some(trx_header_data) = file_data.get(offset..) {\n        if let Ok(trx_header) = parse_trx_header(trx_header_data) {\n            let crc_data_start = offset + CRC_DATA_START_OFFSET;\n            let crc_data_end = crc_data_start + trx_header.total_size - CRC_DATA_START_OFFSET;\n\n            if let Some(crc_data) = file_data.get(crc_data_start..crc_data_end) {\n                if trx_crc32(crc_data) == trx_header.checksum {\n                    result.success = true;\n                    result.size = Some(trx_header.total_size);\n\n                    // If extraction was requested, carve the TRX partitions\n                    if output_directory.is_some() {\n                        let chroot = Chroot::new(output_directory);\n\n                        for i in 0..trx_header.partitions.len() {\n                            let next_partition: usize = i + 1;\n                            let this_partition_relative_offset: usize = trx_header.partitions[i];\n                            let this_partition_absolute_offset: usize =\n                                offset + this_partition_relative_offset;\n                            let mut this_partition_size: usize =\n                                trx_header.total_size - this_partition_relative_offset;\n\n                            if next_partition < trx_header.partitions.len() {\n                                this_partition_size = trx_header.partitions[next_partition]\n                                    - this_partition_relative_offset;\n                            }\n\n                            let this_partition_file_name = format!(\"partition_{i}.bin\");\n                            result.success = chroot.carve_file(\n                                &this_partition_file_name,\n                                file_data,\n                                this_partition_absolute_offset,\n                                this_partition_size,\n                            );\n\n                            if !result.success {\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n\nfn trx_crc32(crc_data: &[u8]) -> usize {\n    (crc32(crc_data) ^ 0xFFFFFFFF) as usize\n}\n"
  },
  {
    "path": "src/extractors/tsk.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the tsk_recover utility to extract various file systems\npub fn tsk_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"tsk_recover\".to_string()),\n        extension: \"img\".to_string(),\n        arguments: vec![\n            \"-i\".to_string(), // Set input type to \"raw\"\n            \"raw\".to_string(),\n            \"-a\".to_string(), // Only recover allocated files\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n            \"rootfs\".to_string(), // Ouput directory\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/ubi.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the ubireader_extract_images utility to extract UBI images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::ubi::ubi_extractor;\n///\n/// match ubi_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn ubi_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\n            \"ubireader_extract_images\".to_string(),\n        ),\n        extension: \"img\".to_string(),\n        arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n\n/// Describes how to run the ubireader_extract_files utility to extract UBIFS images\npub fn ubifs_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"ubireader_extract_files\".to_string()),\n        extension: \"ubifs\".to_string(),\n        arguments: vec![extractors::common::SOURCE_FILE_PLACEHOLDER.to_string()],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/uefi.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the uefi-firmware-parser utility to extract UEFI images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::uefi::uefi_extractor;\n///\n/// match uefi_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn uefi_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"uefi-firmware-parser\".to_string()),\n        extension: \"img\".to_string(),\n        arguments: vec![\n            \"-o.\".to_string(), // Output to the current working directory\n            \"-q\".to_string(),  // Don't print verbose output\n            \"-e\".to_string(),  // Extract\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        /*\n         * This extractor recursively pulls out all the UEFI stuff *and* leaves raw copies of the extracted data on disk.\n         * Recursing into this data would result in double extractions for no good reason.\n         */\n        do_not_recurse: true,\n    }\n}\n"
  },
  {
    "path": "src/extractors/uimage.rs",
    "content": "use crate::common::crc32;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::uimage::parse_uimage_header;\n\n/// Describes the internal extractor for carving uImage files to disk\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::uimage::uimage_extractor;\n///\n/// match uimage_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn uimage_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(extract_uimage),\n        ..Default::default()\n    }\n}\n\npub fn extract_uimage(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // If no name is povided in the uImage header, use this as the output file name\n    const DEFAULT_OUTPUT_FILE_NAME: &str = \"uimage_data\";\n    const OUTPUT_FILE_EXT: &str = \"bin\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Get the uImage data and parse the header\n    if let Some(uimage_header_data) = file_data.get(offset..) {\n        if let Ok(uimage_header) = parse_uimage_header(uimage_header_data) {\n            let image_data_start = offset + uimage_header.header_size;\n            let image_data_end = image_data_start + uimage_header.data_size;\n\n            // Get the raw image data after the uImage header to validate the data CRC\n            if let Some(image_data) = file_data.get(image_data_start..image_data_end) {\n                result.success = true;\n                result.size = Some(uimage_header.header_size);\n\n                // Check the data CRC\n                let data_crc_valid: bool =\n                    crc32(image_data) == (uimage_header.data_checksum as u32);\n\n                // If the data CRC is valid, include the size of the data in the reported size\n                if data_crc_valid {\n                    result.size = Some(result.size.unwrap() + uimage_header.data_size);\n                }\n\n                // If extraction was requested and the data CRC is valid, carve the uImage data out to a file\n                if data_crc_valid && output_directory.is_some() {\n                    let chroot = Chroot::new(output_directory);\n                    let mut file_base_name: String = DEFAULT_OUTPUT_FILE_NAME.to_string();\n\n                    // Use the name specified in the uImage header as the file name, if one was provided\n                    if !uimage_header.name.is_empty() {\n                        file_base_name = uimage_header.name.replace(\" \", \"_\");\n                    }\n\n                    let output_file = format!(\"{file_base_name}.{OUTPUT_FILE_EXT}\");\n\n                    result.success = chroot.create_file(&output_file, image_data);\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/vxworks.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::vxworks::{\n    VxWorksSymbolTableEntry, get_symtab_endianness, parse_symtab_entry,\n};\nuse log::error;\nuse serde_json;\n\n/// Describes the VxWorks symbol table extractor\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::vxworks::vxworks_symtab_extractor;\n///\n/// match vxworks_symtab_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn vxworks_symtab_extractor() -> Extractor {\n    Extractor {\n        do_not_recurse: true,\n        utility: ExtractorType::Internal(extract_symbol_table),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for writing VxWorks symbol tables to JSON\npub fn extract_symbol_table(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    const MIN_VALID_ENTRIES: usize = 250;\n    const OUTFILE_NAME: &str = \"symtab.json\";\n\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    let available_data = file_data.len();\n    let mut previous_entry_offset = None;\n    let mut symtab_entry_offset: usize = offset;\n    let mut symtab_entries: Vec<VxWorksSymbolTableEntry> = vec![];\n\n    // Determine the symbol table endianness first\n    if let Ok(endianness) = get_symtab_endianness(&file_data[symtab_entry_offset..]) {\n        // Loop through all the symbol table entries, until we run out of data or hit an invalid entry\n        while is_offset_safe(available_data, symtab_entry_offset, previous_entry_offset) {\n            // Parse the symbol table entry\n            match parse_symtab_entry(&file_data[symtab_entry_offset..], &endianness) {\n                // Break on an invalid entry\n                Err(_) => {\n                    break;\n                }\n\n                // Increment symtab_entry_offset to the offset of the next entry and keep a list of all processed entries\n                Ok(entry) => {\n                    previous_entry_offset = Some(symtab_entry_offset);\n                    symtab_entry_offset += entry.size;\n                    symtab_entries.push(entry);\n                }\n            }\n        }\n    }\n\n    // Sanity check the number of symbols in the symbol table; there are usualy MANY\n    if symtab_entries.len() >= MIN_VALID_ENTRIES {\n        result.success = true;\n        result.size = Some(symtab_entry_offset - offset);\n\n        // This is not a drill!\n        if output_directory.is_some() {\n            let chroot = Chroot::new(output_directory);\n\n            // Convert symbol table entires to JSON\n            match serde_json::to_string_pretty(&symtab_entries) {\n                // This should never happen...\n                Err(e) => {\n                    error!(\"Failed to convert VxWorks symbol table to JSON: {e}\");\n                }\n\n                // Write JSON to file\n                Ok(symtab_json) => {\n                    result.success =\n                        chroot.create_file(OUTFILE_NAME, &symtab_json.clone().into_bytes());\n                }\n            }\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "src/extractors/wince.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};\nuse crate::structures::wince::{parse_wince_block_header, parse_wince_header};\n\n/// Defines the internal extractor function for extracting Windows CE images\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::wince::wince_extractor;\n///\n/// match wince_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn wince_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(wince_dump),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for extracting data blocks from Windows CE images\npub fn wince_dump(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    let mut result = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Parse the file header\n    if let Some(wince_data) = file_data.get(offset..) {\n        if let Ok(wince_header) = parse_wince_header(wince_data) {\n            // Get the block data, immediately following the file header\n            if let Some(wince_block_data) = wince_data.get(wince_header.header_size..) {\n                // Process all blocks in the block data\n                if let Some(data_blocks) = process_wince_blocks(wince_block_data) {\n                    // The first block entry's address should equal the WinCE header's base address\n                    if data_blocks.entries[0].address == wince_header.base_address {\n                        // Block processing was successful\n                        result.success = true;\n                        result.size = Some(wince_header.header_size + data_blocks.total_size);\n\n                        // If extraction was requested, extract each block to a file on disk\n                        if output_directory.is_some() {\n                            let chroot = Chroot::new(output_directory);\n\n                            for block in data_blocks.entries {\n                                let block_file_name = format!(\"{:X}.bin\", block.address);\n\n                                // If file carving fails, report a failure to extract\n                                if !chroot.carve_file(\n                                    block_file_name,\n                                    wince_block_data,\n                                    block.offset,\n                                    block.size,\n                                ) {\n                                    result.success = false;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    result\n}\n\n/// Stores info about each WinCE block\n#[derive(Debug, Default, Clone)]\nstruct BlockInfo {\n    pub address: usize,\n    pub offset: usize,\n    pub size: usize,\n}\n\n/// Stores info about all WinCE blocks\n#[derive(Debug, Default, Clone)]\nstruct BlockData {\n    pub total_size: usize,\n    pub entries: Vec<BlockInfo>,\n}\n\n/// Process all WinCE blocks\nfn process_wince_blocks(blocks_data: &[u8]) -> Option<BlockData> {\n    // Arbitrarily chosen, just to make sure more than one or two blocks were processed and sane\n    const MIN_ENTRIES_COUNT: usize = 5;\n\n    let mut blocks = BlockData {\n        ..Default::default()\n    };\n\n    let mut next_offset: usize = 0;\n    let mut previous_offset = None;\n    let available_data = blocks_data.len();\n\n    // Process all blocks until the end block is reached, or an error is encountered\n    while is_offset_safe(available_data, next_offset, previous_offset) {\n        // Parse this block's header\n        match parse_wince_block_header(&blocks_data[next_offset..]) {\n            Err(_) => {\n                break;\n            }\n            Ok(block_header) => {\n                // Include the block header size in the total size of the block data\n                blocks.total_size += block_header.header_size;\n\n                // A block header address of NULL indicates EOF\n                if block_header.address == 0 {\n                    // Sanity check the number of blocks processed\n                    if blocks.entries.len() > MIN_ENTRIES_COUNT {\n                        return Some(blocks);\n                    } else {\n                        break;\n                    }\n                } else {\n                    // Include this block's size in the total size of the block data\n                    blocks.total_size += block_header.data_size;\n\n                    // Add this block to the list of block entries\n                    blocks.entries.push(BlockInfo {\n                        address: block_header.address,\n                        offset: next_offset + block_header.header_size,\n                        size: block_header.data_size,\n                    });\n\n                    // Update the offsets for the next loop iteration\n                    previous_offset = Some(next_offset);\n                    next_offset += block_header.header_size + block_header.data_size;\n                }\n            }\n        }\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/extractors/yaffs2.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the unyaffs utility to extract YAFFS2 file systems\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::yaffs2::yaffs2_extractor;\n///\n/// match yaffs2_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn yaffs2_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"unyaffs\".to_string()),\n        extension: \"img\".to_string(),\n        arguments: vec![\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n            \"yaffs-root\".to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors/zlib.rs",
    "content": "use crate::extractors::common::{ExtractionResult, Extractor, ExtractorType};\nuse crate::extractors::inflate;\n\n/// Size of the checksum that follows the ZLIB deflate data stream\npub const CHECKSUM_SIZE: usize = 4;\n\n/// Defines the internal extractor function for decompressing zlib data\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::zlib::zlib_extractor;\n///\n/// match zlib_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn zlib_extractor() -> Extractor {\n    Extractor {\n        utility: ExtractorType::Internal(zlib_decompress),\n        ..Default::default()\n    }\n}\n\n/// Internal extractor for decompressing ZLIB data\npub fn zlib_decompress(\n    file_data: &[u8],\n    offset: usize,\n    output_directory: Option<&str>,\n) -> ExtractionResult {\n    // Size of the zlib header\n    const HEADER_SIZE: usize = 2;\n\n    let mut exresult = ExtractionResult {\n        ..Default::default()\n    };\n\n    // Do the decompression, ignoring the ZLIB header\n    let inflate_result =\n        inflate::inflate_decompressor(file_data, offset + HEADER_SIZE, output_directory);\n\n    // Check that the data decompressed OK\n    if inflate_result.success {\n        // Calculate the ZLIB checksum offsets\n        let checksum_start = offset + HEADER_SIZE + inflate_result.size;\n        let checksum_end = checksum_start + CHECKSUM_SIZE;\n\n        // Get the ZLIB checksum\n        if let Some(adler32_checksum_bytes) = file_data.get(checksum_start..checksum_end) {\n            let reported_checksum = u32::from_be_bytes(adler32_checksum_bytes.try_into().unwrap());\n\n            // Make sure the checksum matches\n            if reported_checksum == inflate_result.adler32 {\n                exresult.success = true;\n                exresult.size = Some(HEADER_SIZE + inflate_result.size + CHECKSUM_SIZE);\n            }\n        }\n    }\n\n    exresult\n}\n"
  },
  {
    "path": "src/extractors/zstd.rs",
    "content": "use crate::extractors;\n\n/// Describes how to run the zstd utility to extract ZSTD compressed files\n///\n/// ```\n/// use std::io::ErrorKind;\n/// use std::process::Command;\n/// use binwalk::extractors::common::ExtractorType;\n/// use binwalk::extractors::zstd::zstd_extractor;\n///\n/// match zstd_extractor().utility {\n///     ExtractorType::None => panic!(\"Invalid extractor type of None\"),\n///     ExtractorType::Internal(func) => println!(\"Internal extractor OK: {:?}\", func),\n///     ExtractorType::External(cmd) => {\n///         if let Err(e) = Command::new(&cmd).output() {\n///             if e.kind() == ErrorKind::NotFound {\n///                 panic!(\"External extractor '{}' not found\", cmd);\n///             } else {\n///                 panic!(\"Failed to execute external extractor '{}': {}\", cmd, e);\n///             }\n///         }\n///     }\n/// }\n/// ```\npub fn zstd_extractor() -> extractors::common::Extractor {\n    extractors::common::Extractor {\n        utility: extractors::common::ExtractorType::External(\"zstd\".to_string()),\n        extension: \"zst\".to_string(),\n        arguments: vec![\n            \"-k\".to_string(), // Don't delete input files (we do this ourselves)\n            \"-f\".to_string(), // Force overwrite if output file, for some reason, exists (disables y/n prompts)\n            \"-d\".to_string(), // Perform a decompression\n            extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),\n        ],\n        exit_codes: vec![0],\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "src/extractors.rs",
    "content": "//! # File Extractors\n//!\n//! File extractors may be internal (written in Rust, compiled into Binwalk), or external (command line utilties).\n//!\n//! While the former are generally faster, safer, and portable, the latter requires very little code to implement.\n//!\n//! Binwalk relies on various internal and external utilities for automated file extraction.\n//!\n//! ## External Extractors\n//!\n//! To implement an external extractor, you must use the `extractors::common::Extractor` struct to define:\n//!\n//! - The name of a command-line utility to run\n//! - What arguments to pass to it\n//! - What file extension the utility expects\n//! - Which exit codes are considered successful (the default is exit code `0`)\n//!\n//! ### Example\n//!\n//! We want to define an external extractor for a new, contrived, file type, `FooBar`. A command-line utility,\n//! `unfoobar`, exists, and is typically executed as such:\n//!\n//! ```bash\n//! unfoobar -x -f input_file.bin -o data.foobar\n//! ```\n//!\n//! To define this external utility as an extractor:\n//!\n//! ```no_run\n//! use binwalk::extractors::common::{Extractor, ExtractorType, SOURCE_FILE_PLACEHOLDER};\n//!\n//! /// This function returns an instance of extractors::common::Extractor, which describes how to run the unfoobar utility.\n//! pub fn foobar_extractor() -> Extractor {\n//!    // Build and return the Extractor struct\n//!    return Extractor {\n//!        // This indicates that we are defining an external extractor, named 'unfoobar'\n//!        utility: ExtractorType::External(\"unfoobar\".to_string()),\n//!        // This is the file extension to use when carving the FooBar file system data to disk\n//!        extension: \"bin\".to_string(),\n//!        // These are the arguments to pass to the unfoobar utility\n//!        arguments: vec![\n//!            \"-x\".to_string(),           // This argument tells unfoobar to extract the FooBar data\n//!            \"-o\".to_string(),           // Specify an output file\n//!            \"data.foobar\".to_string(),  // The output file name\n//!            \"-f\".to_string(),           // Specify an input file\n//!            // This is a special string that will be replaced at run-time with the name of the source file\n//!            SOURCE_FILE_PLACEHOLDER.to_string()\n//!        ],\n//!        // The only valid exit code for this utility is 0\n//!        exit_codes: vec![0],\n//!        // If set to true, the extracted files will not be analyzed\n//!        do_not_recurse: false,\n//!        ..Default::default()\n//!    };\n//! }\n//! ```\n//!\n//! ## Internal Extractors\n//!\n//! Internal extractors are functions that are repsonsible for extracting the data of a particular file type.\n//! They must conform to the `extractors::common::InternalExtractor` type definition.\n//!\n//! Like external extractors, they are defined using the `extractors::common::Extractor` struct.\n//!\n//! The internal extraction function will be passed:\n//!\n//! - The entirety of the file data\n//! - An offset inside the file data at which to begin processing data\n//! - An output directory for extracted files (optional)\n//!\n//! If the output directory is `None`, the extractor function should perform a \"dry run\", processing the intended file format\n//! as normal, but must not extract any data; this allows signatures to use the extractor function to validate potential signature\n//! matches without performing an actual extraction.\n//!\n//! Internal extractors must return an `extractors::common::ExtractionResult` struct.\n//!\n//! Internal extractors should use the `extractors::common::Chroot` API to write files to disk.\n//! The methods defined in the `Chroot` struct allow the manipulation of files on disk while ensuring that any file paths\n//! accessed do not traverse outside the specified output directory.\n//!\n//! ### Example\n//!\n//! ```ignore\n//! use binwalk::common::crc32;\n//! use binwalk::extractors::common::{Chroot, Extractor, ExtractionResult, ExtractorType};\n//! use binwalk::structures::foobar::parse_foobar_header;\n//!\n//! /// This function *defines* an internal extractor; it is not the actual extractor\n//! pub fn foobar_extractor() -> Extractor {\n//!    // Build and return the Extractor struct\n//!     return Extractor {\n//!         // This specifies the function extract_foobar_file as the internal extractor to use\n//!         utility: ExtractorType::Internal(extract_foobar_file),\n//!         ..Default::default()\n//!     };\n//! }\n//!\n//! /// This function extracts the contents of a FooBar file\n//! pub fn extract_foobar_file(file_data: Vec<u8>, offset: usize, output_directory: Option<&str>) -> ExtractionResult {\n//!\n//!     // This will be the return value\n//!     let mut result = ExtractionResult{..Default::default()};\n//!\n//!     // Get the FooBar file data, which starts at the specified offset\n//!     if let Some(foobar_data) = file_data.get(offset..) {\n//!         // Parse and validate the FooBar file header; this function is defined in the structures module\n//!         if let Ok(foobar_header) = parse_foobar_header(foobar_data) {\n//!             // Data CRC is calculated over data_size bytes, starting at the end of the FooBar header\n//!             let crc_start = foobar_header.header_size;\n//!             let crc_end = crc_start + foobar_header.data_size;\n//!\n//!             if let Some(crc_data) = foobar_data.get(crc_start..crc_end){\n//!                 // Validate the data CRC\n//!                 if foobar_header.data_crc == (crc32(crc_data) as usize) {\n//!                     // Report the total size of the FooBar file, including header and data\n//!                     result.size = Some(foobar_header.header_size + foobar_header.data_size);\n//!\n//!                     // If an output directory was specified, extract the contents of the FooBar file to disk\n//!                     if !output_directory.is_none() {\n//!                         // Chroot file I/O inside the specified output directory\n//!                         let chroot = Chroot::new(output_directory);\n//!\n//!                         // The FooBar file format is very simple: just a header, followed by the data we want to extract.\n//!                         // Carve the FooBar data to disk, and set result.success to true if this succeeds.\n//!                         result.success = chroot.carve_file(foobar_header.original_file_name,\n//!                                                            foobar_data,\n//!                                                            foobar_header.header_size,\n//!                                                            foobar_header.data_size);\n//!                     } else {\n//!                         // Nothing else to do, consider this a success\n//!                         result.success = true;\n//!                     }\n//!                 }\n//!             }\n//!         }\n//!     }\n//!\n//!     return result;\n//! }\n//! ```\n\npub mod androidsparse;\npub mod arcadyan;\npub mod autel;\npub mod bmp;\npub mod bzip2;\npub mod cab;\npub mod common;\npub mod csman;\npub mod dahua_zip;\npub mod dmg;\npub mod dtb;\npub mod dumpifs;\npub mod dxbc;\npub mod encfw;\npub mod gif;\npub mod gpg;\npub mod gzip;\npub mod inflate;\npub mod iso9660;\npub mod jboot;\npub mod jffs2;\npub mod jpeg;\npub mod linux;\npub mod lz4;\npub mod lzfse;\npub mod lzma;\npub mod lzop;\npub mod matter_ota;\npub mod mbr;\npub mod mh01;\npub mod pcap;\npub mod pem;\npub mod png;\npub mod rar;\npub mod riff;\npub mod romfs;\npub mod sevenzip;\npub mod squashfs;\npub mod srec;\npub mod svg;\npub mod swapped;\npub mod tarball;\npub mod trx;\npub mod tsk;\npub mod ubi;\npub mod uefi;\npub mod uimage;\npub mod vxworks;\npub mod wince;\npub mod yaffs2;\npub mod zlib;\npub mod zstd;\n"
  },
  {
    "path": "src/json.rs",
    "content": "use log::error;\nuse serde::{Deserialize, Serialize};\nuse std::fs;\nuse std::io;\nuse std::io::Seek;\nuse std::io::Write;\n\nuse crate::binwalk::AnalysisResults;\nuse crate::display;\nuse crate::entropy::FileEntropy;\n\nconst STDOUT: &str = \"-\";\nconst JSON_LIST_START: &str = \"[\\n\";\nconst JSON_LIST_END: &str = \"\\n]\\n\";\nconst JSON_LIST_SEP: &str = \",\\n\";\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum JSONType {\n    Entropy(FileEntropy),\n    Analysis(AnalysisResults),\n}\n\n#[derive(Debug, Default, Clone)]\npub struct JsonLogger {\n    pub json_file: Option<String>,\n    pub json_file_initialized: bool,\n}\n\nimpl JsonLogger {\n    pub fn new(log_file: Option<String>) -> JsonLogger {\n        let mut new_instance = JsonLogger {\n            ..Default::default()\n        };\n\n        if log_file.is_some() {\n            new_instance.json_file = Some(log_file.unwrap().clone());\n        }\n\n        new_instance\n    }\n\n    pub fn close(&self) {\n        self.write_json(JSON_LIST_END);\n    }\n\n    pub fn log(&mut self, results: JSONType) {\n        // Convert analysis results to JSON\n        match serde_json::to_string_pretty(&results) {\n            Err(e) => error!(\"Failed to convert analysis results to JSON: {e}\"),\n            Ok(json) => {\n                if !self.json_file_initialized {\n                    self.write_json(JSON_LIST_START);\n                    self.json_file_initialized = true;\n                } else {\n                    self.write_json(JSON_LIST_SEP);\n                }\n                self.write_json(&json);\n            }\n        }\n    }\n\n    fn write_json(&self, data: &str) {\n        if let Some(log_file) = &self.json_file {\n            if log_file == STDOUT {\n                display::print_plain(false, data);\n            } else {\n                // Open file for reading and writing, create if does not already exist\n                match fs::OpenOptions::new()\n                    .create(true)\n                    .append(true)\n                    .read(true)\n                    .open(log_file)\n                {\n                    Err(e) => {\n                        error!(\"Failed to open JSON log file '{log_file}': {e}\");\n                    }\n                    Ok(mut fp) => {\n                        // Seek to the end of the file and get the cursor position\n                        match fp.seek(io::SeekFrom::End(0)) {\n                            Err(e) => {\n                                error!(\"Failed to seek to end of JSON file: {e}\");\n                            }\n                            Ok(_) => {\n                                if let Err(e) = fp.write_all(data.as_bytes()) {\n                                    error!(\"Failed to write to JSON log file: {e}\");\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! Rust library for identifying, and optionally extracting, files embedded inside other files.\n//!\n//! ## Example\n//!\n//!```no_run\n//! use binwalk::Binwalk;\n//!\n//! // Create a new Binwalk instance\n//! let binwalker = Binwalk::new();\n//!\n//! // Read in the data you want to analyze\n//! let file_data = std::fs::read(\"/tmp/firmware.bin\").expect(\"Failed to read from file\");\n//!\n//! // Scan the file data and print the results\n//! for result in binwalker.scan(&file_data) {\n//!    println!(\"{:#?}\", result);\n//! }\n//! ```\nmod binwalk;\npub mod common;\npub mod extractors;\nmod magic;\npub mod signatures;\npub mod structures;\npub use binwalk::{AnalysisResults, Binwalk, BinwalkError};\n"
  },
  {
    "path": "src/magic.rs",
    "content": "use crate::extractors;\nuse crate::signatures;\n\n/// Returns a list of all supported signatures, including their \"magic\" byte patterns, parser functions, and any associated extractor.\npub fn patterns() -> Vec<signatures::common::Signature> {\n    let binary_signatures: Vec<signatures::common::Signature> = vec![\n        // gzip\n        signatures::common::Signature {\n            name: \"gzip\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::gzip::gzip_magic(),\n            parser: signatures::gzip::gzip_parser,\n            description: signatures::gzip::DESCRIPTION.to_string(),\n            extractor: Some(extractors::gzip::gzip_extractor()),\n        },\n        // .deb\n        signatures::common::Signature {\n            name: \"deb\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::deb::deb_magic(),\n            parser: signatures::deb::deb_parser,\n            description: signatures::deb::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // 7-zip\n        signatures::common::Signature {\n            name: \"7zip\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::sevenzip::sevenzip_magic(),\n            parser: signatures::sevenzip::sevenzip_parser,\n            description: signatures::sevenzip::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // xz\n        signatures::common::Signature {\n            name: \"xz\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::xz::xz_magic(),\n            parser: signatures::xz::xz_parser,\n            description: signatures::xz::DESCRIPTION.to_string(),\n            extractor: Some(extractors::lzma::lzma_extractor()),\n        },\n        // tarball\n        signatures::common::Signature {\n            name: \"tarball\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::tarball::tarball_magic(),\n            parser: signatures::tarball::tarball_parser,\n            description: signatures::tarball::DESCRIPTION.to_string(),\n            extractor: Some(extractors::tarball::tarball_extractor()),\n        },\n        // squashfs\n        signatures::common::Signature {\n            name: \"squashfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::squashfs::squashfs_magic(),\n            parser: signatures::squashfs::squashfs_parser,\n            description: signatures::squashfs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::squashfs::squashfs_extractor()),\n        },\n        // dlob\n        signatures::common::Signature {\n            name: \"dlob\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dlob::dlob_magic(),\n            parser: signatures::dlob::dlob_parser,\n            description: signatures::dlob::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // lzma\n        signatures::common::Signature {\n            name: \"lzma\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::lzma::lzma_magic(),\n            parser: signatures::lzma::lzma_parser,\n            description: signatures::lzma::DESCRIPTION.to_string(),\n            //extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n            extractor: Some(extractors::lzma::lzma_extractor()),\n        },\n        // bmp\n        signatures::common::Signature {\n            name: \"bmp\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::bmp::bmp_magic(),\n            parser: signatures::bmp::bmp_parser,\n            description: signatures::bmp::DESCRIPTION.to_string(),\n            extractor: Some(extractors::bmp::bmp_extractor()),\n        },\n        // bzip2\n        signatures::common::Signature {\n            name: \"bzip2\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::bzip2::bzip2_magic(),\n            parser: signatures::bzip2::bzip2_parser,\n            description: signatures::bzip2::DESCRIPTION.to_string(),\n            extractor: Some(extractors::bzip2::bzip2_extractor()),\n        },\n        // uimage\n        signatures::common::Signature {\n            name: \"uimage\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::uimage::uimage_magic(),\n            parser: signatures::uimage::uimage_parser,\n            description: signatures::uimage::DESCRIPTION.to_string(),\n            extractor: Some(extractors::uimage::uimage_extractor()),\n        },\n        // packimg header\n        signatures::common::Signature {\n            name: \"packimg\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::packimg::packimg_magic(),\n            parser: signatures::packimg::packimg_parser,\n            description: signatures::packimg::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // crc32 constants\n        signatures::common::Signature {\n            name: \"crc32\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::hashes::crc32_magic(),\n            parser: signatures::hashes::crc32_parser,\n            description: signatures::hashes::CRC32_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // sha256 constants\n        signatures::common::Signature {\n            name: \"sha256\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::hashes::sha256_magic(),\n            parser: signatures::hashes::sha256_parser,\n            description: signatures::hashes::SHA256_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // cpio\n        signatures::common::Signature {\n            name: \"cpio\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::cpio::cpio_magic(),\n            parser: signatures::cpio::cpio_parser,\n            description: signatures::cpio::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // iso9660 primary volume\n        signatures::common::Signature {\n            name: \"iso9660\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::iso9660::iso_magic(),\n            parser: signatures::iso9660::iso_parser,\n            description: signatures::iso9660::DESCRIPTION.to_string(),\n            extractor: Some(extractors::iso9660::iso9660_extractor()),\n        },\n        // linux kernel\n        signatures::common::Signature {\n            name: \"linux_kernel\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::linux::linux_kernel_version_magic(),\n            parser: signatures::linux::linux_kernel_version_parser,\n            description: signatures::linux::LINUX_KERNEL_VERSION_DESCRIPTION.to_string(),\n            extractor: Some(extractors::linux::linux_kernel_extractor()),\n        },\n        // linux boot image\n        signatures::common::Signature {\n            name: \"linux_boot_image\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::linux::linux_boot_image_magic(),\n            parser: signatures::linux::linux_boot_image_parser,\n            description: signatures::linux::LINUX_BOOT_IMAGE_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // linux arm zimage\n        signatures::common::Signature {\n            name: \"linux_arm_zimage\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::linux::linux_arm_zimage_magic(),\n            parser: signatures::linux::linux_arm_zimage_parser,\n            description: signatures::linux::LINUX_ARM_ZIMAGE_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // zstd\n        signatures::common::Signature {\n            name: \"zstd\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::zstd::zstd_magic(),\n            parser: signatures::zstd::zstd_parser,\n            description: signatures::zstd::DESCRIPTION.to_string(),\n            extractor: Some(extractors::zstd::zstd_extractor()),\n        },\n        // zip\n        signatures::common::Signature {\n            name: \"zip\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::zip::zip_magic(),\n            parser: signatures::zip::zip_parser,\n            description: signatures::zip::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // Intel PCH ROM\n        signatures::common::Signature {\n            name: \"pchrom\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pchrom::pch_rom_magic(),\n            parser: signatures::pchrom::pch_rom_parser,\n            description: signatures::pchrom::DESCRIPTION.to_string(),\n            extractor: Some(extractors::uefi::uefi_extractor()),\n        },\n        // UEFI PI volume\n        signatures::common::Signature {\n            name: \"uefi_pi_volume\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::uefi::uefi_volume_magic(),\n            parser: signatures::uefi::uefi_volume_parser,\n            description: signatures::uefi::VOLUME_DESCRIPTION.to_string(),\n            extractor: Some(extractors::uefi::uefi_extractor()),\n        },\n        // UEFI capsule image\n        signatures::common::Signature {\n            name: \"uefi_capsule\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::uefi::uefi_capsule_magic(),\n            parser: signatures::uefi::uefi_capsule_parser,\n            description: signatures::uefi::CAPSULE_DESCRIPTION.to_string(),\n            extractor: Some(extractors::uefi::uefi_extractor()),\n        },\n        // PDF document\n        signatures::common::Signature {\n            name: \"pdf\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pdf::pdf_magic(),\n            parser: signatures::pdf::pdf_parser,\n            description: signatures::pdf::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // ELF\n        signatures::common::Signature {\n            name: \"elf\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::elf::elf_magic(),\n            parser: signatures::elf::elf_parser,\n            description: signatures::elf::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // CramFS\n        signatures::common::Signature {\n            name: \"cramfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::cramfs::cramfs_magic(),\n            parser: signatures::cramfs::cramfs_parser,\n            description: signatures::cramfs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // QNX IFS\n        // TODO: The signature and extractor are untested. Need a sample IFS image.\n        signatures::common::Signature {\n            name: \"qnx_ifs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::qnx::qnx_ifs_magic(),\n            parser: signatures::qnx::qnx_ifs_parser,\n            description: signatures::qnx::IFS_DESCRIPTION.to_string(),\n            extractor: Some(extractors::dumpifs::dumpifs_extractor()),\n        },\n        // RomFS\n        signatures::common::Signature {\n            name: \"romfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::romfs::romfs_magic(),\n            parser: signatures::romfs::romfs_parser,\n            description: signatures::romfs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::romfs::romfs_extractor()),\n        },\n        // EXT\n        signatures::common::Signature {\n            name: \"ext\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::ext::ext_magic(),\n            parser: signatures::ext::ext_parser,\n            description: signatures::ext::DESCRIPTION.to_string(),\n            extractor: Some(extractors::tsk::tsk_extractor()),\n        },\n        // CAB archive\n        signatures::common::Signature {\n            name: \"cab\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::cab::cab_magic(),\n            parser: signatures::cab::cab_parser,\n            description: signatures::cab::DESCRIPTION.to_string(),\n            extractor: Some(extractors::cab::cab_extractor()),\n        },\n        // JFFS2\n        signatures::common::Signature {\n            name: \"jffs2\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::jffs2::jffs2_magic(),\n            parser: signatures::jffs2::jffs2_parser,\n            description: signatures::jffs2::DESCRIPTION.to_string(),\n            extractor: Some(extractors::jffs2::jffs2_extractor()),\n        },\n        // YAFFS\n        signatures::common::Signature {\n            name: \"yaffs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::yaffs::yaffs_magic(),\n            parser: signatures::yaffs::yaffs_parser,\n            description: signatures::yaffs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::yaffs2::yaffs2_extractor()),\n        },\n        // lz4\n        signatures::common::Signature {\n            name: \"lz4\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::lz4::lz4_magic(),\n            parser: signatures::lz4::lz4_parser,\n            description: signatures::lz4::DESCRIPTION.to_string(),\n            extractor: Some(extractors::lz4::lz4_extractor()),\n        },\n        // lzop\n        signatures::common::Signature {\n            name: \"lzop\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::lzop::lzop_magic(),\n            parser: signatures::lzop::lzop_parser,\n            description: signatures::lzop::DESCRIPTION.to_string(),\n            extractor: Some(extractors::lzop::lzop_extractor()),\n        },\n        // lzop\n        signatures::common::Signature {\n            name: \"pe\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pe::pe_magic(),\n            parser: signatures::pe::pe_parser,\n            description: signatures::pe::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // zlib\n        signatures::common::Signature {\n            name: \"zlib\".to_string(),\n            // The magic bytes for this signature are only 2 bytes, only match on the beginning of a file\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::zlib::zlib_magic(),\n            parser: signatures::zlib::zlib_parser,\n            description: signatures::zlib::DESCRIPTION.to_string(),\n            extractor: Some(extractors::zlib::zlib_extractor()),\n        },\n        // gpg signed data\n        signatures::common::Signature {\n            name: \"gpg_signed\".to_string(),\n            // The magic bytes for this signature are only 2 bytes, only match on the beginning of a file\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::gpg::gpg_signed_magic(),\n            parser: signatures::gpg::gpg_signed_parser,\n            description: signatures::gpg::GPG_SIGNED_DESCRIPTION.to_string(),\n            extractor: Some(extractors::gpg::gpg_extractor()),\n        },\n        // pem certificates\n        signatures::common::Signature {\n            name: \"pem_certificate\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pem::pem_certificate_magic(),\n            parser: signatures::pem::pem_parser,\n            description: signatures::pem::PEM_CERTIFICATE_DESCRIPTION.to_string(),\n            extractor: Some(extractors::pem::pem_certificate_extractor()),\n        },\n        // pem public keys\n        signatures::common::Signature {\n            name: \"pem_public_key\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::pem::pem_public_key_magic(),\n            parser: signatures::pem::pem_parser,\n            description: signatures::pem::PEM_PUBLIC_KEY_DESCRIPTION.to_string(),\n            extractor: Some(extractors::pem::pem_key_extractor()),\n        },\n        // pem private keys\n        signatures::common::Signature {\n            name: \"pem_private_key\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::pem::pem_private_key_magic(),\n            parser: signatures::pem::pem_parser,\n            description: signatures::pem::PEM_PRIVATE_KEY_DESCRIPTION.to_string(),\n            extractor: Some(extractors::pem::pem_key_extractor()),\n        },\n        // netgear chk\n        signatures::common::Signature {\n            name: \"chk\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::chk::chk_magic(),\n            parser: signatures::chk::chk_parser,\n            description: signatures::chk::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // trx\n        signatures::common::Signature {\n            name: \"trx\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::trx::trx_magic(),\n            parser: signatures::trx::trx_parser,\n            description: signatures::trx::DESCRIPTION.to_string(),\n            extractor: Some(extractors::trx::trx_extractor()),\n        },\n        // Motorola S-record\n        signatures::common::Signature {\n            name: \"srecord\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::srec::srec_magic(),\n            parser: signatures::srec::srec_parser,\n            description: signatures::srec::SREC_DESCRIPTION.to_string(),\n            extractor: Some(extractors::srec::srec_extractor()),\n        },\n        // Motorola S-record (generic)\n        signatures::common::Signature {\n            name: \"srecord_generic\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::srec::srec_short_magic(),\n            parser: signatures::srec::srec_parser,\n            description: signatures::srec::SREC_SHORT_DESCRIPTION.to_string(),\n            extractor: Some(extractors::srec::srec_extractor()),\n        },\n        // Android sparse\n        signatures::common::Signature {\n            name: \"android_sparse\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::androidsparse::android_sparse_magic(),\n            parser: signatures::androidsparse::android_sparse_parser,\n            description: signatures::androidsparse::DESCRIPTION.to_string(),\n            extractor: Some(extractors::androidsparse::android_sparse_extractor()),\n        },\n        // device tree blob\n        signatures::common::Signature {\n            name: \"dtb\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dtb::dtb_magic(),\n            parser: signatures::dtb::dtb_parser,\n            description: signatures::dtb::DESCRIPTION.to_string(),\n            extractor: Some(extractors::dtb::dtb_extractor()),\n        },\n        // ubi\n        signatures::common::Signature {\n            name: \"ubi\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::ubi::ubi_magic(),\n            parser: signatures::ubi::ubi_parser,\n            description: signatures::ubi::UBI_IMAGE_DESCRIPTION.to_string(),\n            extractor: Some(extractors::ubi::ubi_extractor()),\n        },\n        // ubifs\n        signatures::common::Signature {\n            name: \"ubifs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::ubi::ubifs_magic(),\n            parser: signatures::ubi::ubifs_parser,\n            description: signatures::ubi::UBI_FS_DESCRIPTION.to_string(),\n            extractor: Some(extractors::ubi::ubifs_extractor()),\n        },\n        // cfe bootloader\n        signatures::common::Signature {\n            name: \"cfe\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::cfe::cfe_magic(),\n            parser: signatures::cfe::cfe_parser,\n            description: signatures::cfe::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // SEAMA firmware header\n        signatures::common::Signature {\n            name: \"seama\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::seama::seama_magic(),\n            parser: signatures::seama::seama_parser,\n            description: signatures::seama::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // compress'd\n        signatures::common::Signature {\n            name: \"compressd\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::compressd::compressd_magic(),\n            parser: signatures::compressd::compressd_parser,\n            description: signatures::compressd::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // rar archive\n        signatures::common::Signature {\n            name: \"rar\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::rar::rar_magic(),\n            parser: signatures::rar::rar_parser,\n            description: signatures::rar::DESCRIPTION.to_string(),\n            extractor: Some(extractors::rar::rar_extractor()),\n        },\n        // PNG image\n        signatures::common::Signature {\n            name: \"png\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::png::png_magic(),\n            parser: signatures::png::png_parser,\n            description: signatures::png::DESCRIPTION.to_string(),\n            extractor: Some(extractors::png::png_extractor()),\n        },\n        // JPEG image\n        signatures::common::Signature {\n            name: \"jpeg\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::jpeg::jpeg_magic(),\n            parser: signatures::jpeg::jpeg_parser,\n            description: signatures::jpeg::DESCRIPTION.to_string(),\n            extractor: Some(extractors::jpeg::jpeg_extractor()),\n        },\n        // arcadyan obfuscated lzma\n        signatures::common::Signature {\n            name: \"arcadyan\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::arcadyan::obfuscated_lzma_magic(),\n            parser: signatures::arcadyan::obfuscated_lzma_parser,\n            description: signatures::arcadyan::DESCRIPTION.to_string(),\n            extractor: Some(extractors::arcadyan::obfuscated_lzma_extractor()),\n        },\n        // copyright text\n        signatures::common::Signature {\n            name: \"copyright\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::copyright::copyright_magic(),\n            parser: signatures::copyright::copyright_parser,\n            description: signatures::copyright::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // WIND kernel version\n        signatures::common::Signature {\n            name: \"wind_kernel\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::vxworks::wind_kernel_magic(),\n            parser: signatures::vxworks::wind_kernel_parser,\n            description: signatures::vxworks::WIND_KERNEL_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // vxworks symbol table\n        signatures::common::Signature {\n            name: \"vxworks_symtab\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::vxworks::symbol_table_magic(),\n            parser: signatures::vxworks::symbol_table_parser,\n            description: signatures::vxworks::SYMTAB_DESCRIPTION.to_string(),\n            extractor: Some(extractors::vxworks::vxworks_symtab_extractor()),\n        },\n        // ecos mips exception handler\n        signatures::common::Signature {\n            name: \"ecos\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::ecos::exception_handler_magic(),\n            parser: signatures::ecos::exception_handler_parser,\n            description: signatures::ecos::EXCEPTION_HANDLER_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // dmg\n        signatures::common::Signature {\n            name: \"dmg\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dmg::dmg_magic(),\n            parser: signatures::dmg::dmg_parser,\n            description: signatures::dmg::DESCRIPTION.to_string(),\n            extractor: Some(extractors::dmg::dmg_extractor()),\n        },\n        // riff\n        signatures::common::Signature {\n            name: \"riff\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::riff::riff_magic(),\n            parser: signatures::riff::riff_parser,\n            description: signatures::riff::DESCRIPTION.to_string(),\n            extractor: Some(extractors::riff::riff_extractor()),\n        },\n        // openssl\n        signatures::common::Signature {\n            name: \"openssl\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::openssl::openssl_crypt_magic(),\n            parser: signatures::openssl::openssl_crypt_parser,\n            description: signatures::openssl::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // lzfse\n        signatures::common::Signature {\n            name: \"lzfse\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::lzfse::lzfse_magic(),\n            parser: signatures::lzfse::lzfse_parser,\n            description: signatures::lzfse::DESCRIPTION.to_string(),\n            extractor: Some(extractors::lzfse::lzfse_extractor()),\n        },\n        // MBR\n        signatures::common::Signature {\n            name: \"mbr\".to_string(),\n            short: true,\n            magic_offset: signatures::mbr::MAGIC_OFFSET,\n            always_display: true,\n            magic: signatures::mbr::mbr_magic(),\n            parser: signatures::mbr::mbr_parser,\n            description: signatures::mbr::DESCRIPTION.to_string(),\n            extractor: Some(extractors::mbr::mbr_extractor()),\n        },\n        // tp-link\n        signatures::common::Signature {\n            name: \"tplink\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::tplink::tplink_magic(),\n            parser: signatures::tplink::tplink_parser,\n            description: signatures::tplink::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // HP PJL\n        signatures::common::Signature {\n            name: \"pjl\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pjl::pjl_magic(),\n            parser: signatures::pjl::pjl_parser,\n            description: signatures::pjl::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // JBOOT ARM firmware image\n        signatures::common::Signature {\n            name: \"jboot_arm\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::jboot::jboot_arm_magic(),\n            parser: signatures::jboot::jboot_arm_parser,\n            description: signatures::jboot::JBOOT_ARM_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // JBOOT STAG header\n        signatures::common::Signature {\n            name: \"jboot_stag\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::jboot::jboot_stag_magic(),\n            parser: signatures::jboot::jboot_stag_parser,\n            description: signatures::jboot::JBOOT_STAG_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // JBOOT SCH2 header\n        signatures::common::Signature {\n            name: \"jboot_sch2\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::jboot::jboot_sch2_magic(),\n            parser: signatures::jboot::jboot_sch2_parser,\n            description: signatures::jboot::JBOOT_SCH2_DESCRIPTION.to_string(),\n            extractor: Some(extractors::jboot::sch2_extractor()),\n        },\n        // pcap-ng\n        signatures::common::Signature {\n            name: \"pcapng\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pcap::pcapng_magic(),\n            parser: signatures::pcap::pcapng_parser,\n            description: signatures::pcap::PCAPNG_DESCRIPTION.to_string(),\n            extractor: Some(extractors::pcap::pcapng_extractor()),\n        },\n        // RSA encrypted data\n        signatures::common::Signature {\n            name: \"rsa\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::rsa::rsa_magic(),\n            parser: signatures::rsa::rsa_parser,\n            description: signatures::rsa::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // GIF image\n        signatures::common::Signature {\n            name: \"gif\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::gif::gif_magic(),\n            parser: signatures::gif::gif_parser,\n            description: signatures::gif::DESCRIPTION.to_string(),\n            extractor: Some(extractors::gif::gif_extractor()),\n        },\n        // SVG image\n        signatures::common::Signature {\n            name: \"svg\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::svg::svg_magic(),\n            parser: signatures::svg::svg_parser,\n            description: signatures::svg::DESCRIPTION.to_string(),\n            extractor: Some(extractors::svg::svg_extractor()),\n        },\n        // Linux ARM64 boot image\n        signatures::common::Signature {\n            name: \"linux_arm64_boot_image\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::linux::linux_arm64_boot_image_magic(),\n            parser: signatures::linux::linux_arm64_boot_image_parser,\n            description: signatures::linux::LINUX_ARM64_BOOT_IMAGE_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // FAT\n        signatures::common::Signature {\n            name: \"fat\".to_string(),\n            short: true,\n            magic_offset: signatures::fat::MAGIC_OFFSET,\n            always_display: false,\n            magic: signatures::fat::fat_magic(),\n            parser: signatures::fat::fat_parser,\n            description: signatures::fat::DESCRIPTION.to_string(),\n            extractor: Some(extractors::tsk::tsk_extractor()),\n        },\n        // EFI GPT\n        signatures::common::Signature {\n            name: \"efigpt\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::efigpt::efigpt_magic(),\n            parser: signatures::efigpt::efigpt_parser,\n            description: signatures::efigpt::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // RTK firmware header\n        signatures::common::Signature {\n            name: \"rtk\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::rtk::rtk_magic(),\n            parser: signatures::rtk::rtk_parser,\n            description: signatures::rtk::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // AES S-Box\n        signatures::common::Signature {\n            name: \"aes_sbox\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::aes::aes_sbox_magic(),\n            parser: signatures::aes::aes_sbox_parser,\n            description: signatures::aes::DESCRIPTION_AES_SBOX.to_string(),\n            extractor: None,\n        },\n        // AES Forward table\n        signatures::common::Signature {\n            name: \"aes_forward_table\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::aes::aes_forward_table_magic(),\n            parser: signatures::aes::aes_forward_table_parser,\n            description: signatures::aes::DESCRIPTION_AES_FT.to_string(),\n            extractor: None,\n        },\n        // AES Reverse table\n        signatures::common::Signature {\n            name: \"aes_reverse_table\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::aes::aes_reverse_table_magic(),\n            parser: signatures::aes::aes_reverse_table_parser,\n            description: signatures::aes::DESCRIPTION_AES_RT.to_string(),\n            extractor: None,\n        },\n        // AES RCON\n        signatures::common::Signature {\n            name: \"aes_rcon\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::aes::aes_rcon_magic(),\n            parser: signatures::aes::aes_rcon_parser,\n            description: signatures::aes::DESCRIPTION_AES_RCON.to_string(),\n            extractor: None,\n        },\n        // Accelerated AES\n        signatures::common::Signature {\n            name: \"aes_acceleration_table\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::aes::aes_acceleration_table_magic(),\n            parser: signatures::aes::aes_acceleration_table_parser,\n            description: signatures::aes::DESCRIPTION_AES_ACC.to_string(),\n            extractor: None,\n        },\n        // LUKS\n        signatures::common::Signature {\n            name: \"luks\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::luks::luks_magic(),\n            parser: signatures::luks::luks_parser,\n            description: signatures::luks::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // TP-Link RTOS\n        signatures::common::Signature {\n            name: \"tplink_rtos\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::tplink::tplink_rtos_magic(),\n            parser: signatures::tplink::tplink_rtos_parser,\n            description: signatures::tplink::RTOS_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // BIN firmware header\n        signatures::common::Signature {\n            name: \"binhdr\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::binhdr::bin_hdr_magic(),\n            parser: signatures::binhdr::bin_hdr_parser,\n            description: signatures::binhdr::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // Autel obfuscated firmware\n        signatures::common::Signature {\n            name: \"autel\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::autel::autel_magic(),\n            parser: signatures::autel::autel_parser,\n            description: signatures::autel::DESCRIPTION.to_string(),\n            extractor: Some(extractors::autel::autel_extractor()),\n        },\n        // NTFS\n        signatures::common::Signature {\n            name: \"ntfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::ntfs::ntfs_magic(),\n            parser: signatures::ntfs::ntfs_parser,\n            description: signatures::ntfs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::tsk::tsk_extractor()),\n        },\n        // APFS\n        signatures::common::Signature {\n            name: \"apfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::apfs::apfs_magic(),\n            parser: signatures::apfs::apfs_parser,\n            description: signatures::apfs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // BTRFS\n        signatures::common::Signature {\n            name: \"btrfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::btrfs::btrfs_magic(),\n            parser: signatures::btrfs::btrfs_parser,\n            description: signatures::btrfs::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // WinCE\n        signatures::common::Signature {\n            name: \"wince\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::wince::wince_magic(),\n            parser: signatures::wince::wince_parser,\n            description: signatures::wince::DESCRIPTION.to_string(),\n            extractor: Some(extractors::wince::wince_extractor()),\n        },\n        // Dahua ZIP\n        signatures::common::Signature {\n            name: \"dahua_zip\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dahua_zip::dahua_zip_magic(),\n            parser: signatures::dahua_zip::dahua_zip_parser,\n            description: signatures::dahua_zip::DESCRIPTION.to_string(),\n            extractor: Some(extractors::dahua_zip::dahua_zip_extractor()),\n        },\n        // DLink MH01\n        signatures::common::Signature {\n            name: \"mh01\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::mh01::mh01_magic(),\n            parser: signatures::mh01::mh01_parser,\n            description: signatures::mh01::DESCRIPTION.to_string(),\n            extractor: Some(extractors::mh01::mh01_extractor()),\n        },\n        // CSman DAT\n        signatures::common::Signature {\n            name: \"csman\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::csman::csman_magic(),\n            parser: signatures::csman::csman_parser,\n            description: signatures::csman::DESCRIPTION.to_string(),\n            extractor: Some(extractors::csman::csman_extractor()),\n        },\n        // DirectX ByteCode\n        signatures::common::Signature {\n            name: \"dxbc\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dxbc::dxbc_magic(),\n            parser: signatures::dxbc::dxbc_parser,\n            description: signatures::dxbc::DESCRIPTION.to_string(),\n            extractor: Some(extractors::dxbc::dxbc_extractor()),\n        },\n        // D-Link TLV firmware\n        signatures::common::Signature {\n            name: \"dlink_tlv\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dlink_tlv::dlink_tlv_magic(),\n            parser: signatures::dlink_tlv::dlink_tlv_parser,\n            description: signatures::dlink_tlv::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // DLKE encrypted firmware\n        signatures::common::Signature {\n            name: \"dlke\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dlke::dlke_magic(),\n            parser: signatures::dlke::dlke_parser,\n            description: signatures::dlke::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // SHRS encrypted firmware\n        signatures::common::Signature {\n            name: \"shrs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::shrs::shrs_magic(),\n            parser: signatures::shrs::shrs_parser,\n            description: signatures::shrs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // PKCS DER hashes\n        signatures::common::Signature {\n            name: \"pkcs_der_hash\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::pkcs_der::der_hash_magic(),\n            parser: signatures::pkcs_der::der_hash_parser,\n            description: signatures::pkcs_der::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // LogFS\n        signatures::common::Signature {\n            name: \"logfs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::logfs::logfs_magic(),\n            parser: signatures::logfs::logfs_parser,\n            description: signatures::logfs::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // encrpted_img\n        signatures::common::Signature {\n            name: \"encrpted_img\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::encrpted_img::encrpted_img_magic(),\n            parser: signatures::encrpted_img::encrpted_img_parser,\n            description: signatures::encrpted_img::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // Android boot image\n        signatures::common::Signature {\n            name: \"android_bootimg\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::android_bootimg::android_bootimg_magic(),\n            parser: signatures::android_bootimg::android_bootimg_parser,\n            description: signatures::android_bootimg::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // uboot\n        signatures::common::Signature {\n            name: \"uboot\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::uboot::uboot_magic(),\n            parser: signatures::uboot::uboot_parser,\n            description: signatures::uboot::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // dms firmware\n        signatures::common::Signature {\n            name: \"dms\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dms::dms_magic(),\n            parser: signatures::dms::dms_parser,\n            description: signatures::dms::DESCRIPTION.to_string(),\n            extractor: Some(extractors::swapped::swapped_extractor_u16()),\n        },\n        // dkbs firmware\n        signatures::common::Signature {\n            name: \"dkbs\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::dkbs::dkbs_magic(),\n            parser: signatures::dkbs::dkbs_parser,\n            description: signatures::dkbs::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // known encrypted firmware\n        signatures::common::Signature {\n            name: \"encfw\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::encfw::encfw_magic(),\n            parser: signatures::encfw::encfw_parser,\n            description: signatures::encfw::DESCRIPTION.to_string(),\n            extractor: Some(extractors::encfw::encfw_extractor()),\n        },\n        // matter ota firmware\n        signatures::common::Signature {\n            name: \"matter_ota\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::matter_ota::matter_ota_magic(),\n            parser: signatures::matter_ota::matter_ota_parser,\n            description: signatures::matter_ota::DESCRIPTION.to_string(),\n            extractor: Some(extractors::matter_ota::matter_ota_extractor()),\n        },\n        // DPAPI blob data\n        signatures::common::Signature {\n            name: \"dpapi\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::dpapi::dpapi_magic(),\n            parser: signatures::dpapi::dpapi_parser,\n            description: signatures::dpapi::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // QEMU QCOW image\n        signatures::common::Signature {\n            name: \"qcow\".to_string(),\n            short: true,\n            magic_offset: 0,\n            always_display: true,\n            magic: signatures::qcow::qcow_magic(),\n            parser: signatures::qcow::qcow_parser,\n            description: signatures::qcow::DESCRIPTION.to_string(),\n            extractor: None,\n        },\n        // ARJ archive\n        signatures::common::Signature {\n            name: \"arj\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::arj::arj_magic(),\n            parser: signatures::arj::arj_parser,\n            description: signatures::arj::DESCRIPTION.to_string(),\n            extractor: Some(extractors::sevenzip::sevenzip_extractor()),\n        },\n        // MD5 hashes\n        signatures::common::Signature {\n            name: \"md5\".to_string(),\n            short: false,\n            magic_offset: 0,\n            always_display: false,\n            magic: signatures::hashes::md5_magic(),\n            parser: signatures::hashes::md5_parser,\n            description: signatures::hashes::MD5_DESCRIPTION.to_string(),\n            extractor: None,\n        },\n    ];\n\n    binary_signatures\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "use binwalk::AnalysisResults;\nuse log::{debug, error, info};\nuse std::collections::VecDeque;\nuse std::panic;\nuse std::process;\nuse std::process::ExitCode;\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time;\nuse threadpool::ThreadPool;\n\nmod binwalk;\nmod cliparser;\nmod common;\nmod display;\nmod entropy;\nmod extractors;\nmod json;\nmod magic;\nmod signatures;\nmod structures;\n\nfn main() -> ExitCode {\n    // File name used when reading from stdin\n    const STDIN: &str = \"stdin\";\n\n    // Only use one thread if unable to auto-detect available core info\n    const DEFAULT_WORKER_COUNT: usize = 1;\n\n    // Number of seconds to wait before printing debug progress info\n    const PROGRESS_INTERVAL: u64 = 30;\n\n    // If this env var is set during extraction, the Binwalk.base_target_file symlink will\n    // be deleted at the end of extraction.\n    const BINWALK_RM_SYMLINK: &str = \"BINWALK_RM_EXTRACTION_SYMLINK\";\n\n    // Output directory for extracted files\n    let mut output_directory: Option<String> = None;\n\n    /*\n     * Maintain a queue of files waiting to be analyzed.\n     * Note that ThreadPool has its own internal queue so this may seem redundant, however,\n     * queuing a large number of jobs via the ThreadPool queue results in *massive* amounts\n     * of unecessary memory consumption, especially when recursively analyzing many files.\n     */\n    let mut target_files = VecDeque::new();\n\n    // Statistics variables; keeps track of analyzed file count and total analysis run time\n    let mut file_count: usize = 0;\n    let run_time = time::Instant::now();\n    let mut last_progress_interval = time::Instant::now();\n\n    // Initialize logging\n    env_logger::init();\n\n    // Process command line arguments\n    let mut cliargs = cliparser::parse();\n\n    // If --list was specified, just display a list of signatures and return\n    if cliargs.list {\n        display::print_signature_list(cliargs.quiet, &magic::patterns());\n        return ExitCode::SUCCESS;\n    }\n\n    // Set a dummy file name when reading from stdin\n    if cliargs.stdin {\n        cliargs.file_name = Some(STDIN.to_string());\n    }\n\n    let mut json_logger = json::JsonLogger::new(cliargs.log);\n\n    // If entropy analysis was requested, generate the entropy graph and return\n    if cliargs.entropy {\n        display::print_plain(cliargs.quiet, \"Calculating file entropy...\");\n\n        if let Ok(entropy_results) =\n            entropy::plot(cliargs.file_name.unwrap(), cliargs.stdin, cliargs.png)\n        {\n            // Log entropy results to JSON file, if requested\n            json_logger.log(json::JSONType::Entropy(entropy_results.clone()));\n            json_logger.close();\n\n            display::println_plain(cliargs.quiet, \"done.\");\n        } else {\n            panic!(\"Entropy analysis failed!\");\n        }\n\n        return ExitCode::SUCCESS;\n    }\n\n    // If extraction or data carving was requested, we need to initialize the output directory\n    if cliargs.extract || cliargs.carve {\n        output_directory = Some(cliargs.directory);\n    }\n\n    // Initialize binwalk\n    let binwalker = match binwalk::Binwalk::configure(\n        cliargs.file_name,\n        output_directory,\n        cliargs.include,\n        cliargs.exclude,\n        None,\n        cliargs.search_all,\n    ) {\n        Err(e) => {\n            error!(\"Binwalk initialization failed: {}\", e.message);\n            return ExitCode::FAILURE;\n        }\n        Ok(bw) => bw,\n    };\n\n    // If the user specified --threads, honor that request; else, auto-detect available parallelism\n    let available_workers = cliargs.threads.unwrap_or_else(|| {\n        // Get CPU core info\n        match thread::available_parallelism() {\n            // In case of error use the default\n            Err(e) => {\n                error!(\"Failed to retrieve CPU core info: {e}\");\n                DEFAULT_WORKER_COUNT\n            }\n            Ok(coreinfo) => coreinfo.get(),\n        }\n    });\n\n    // Sanity check the number of available worker threads\n    if available_workers < 1 {\n        panic!(\"No available worker threads!\");\n    }\n\n    // Initialize thread pool\n    debug!(\"Initializing thread pool with {available_workers} workers\");\n    let workers = ThreadPool::new(available_workers);\n    let (worker_tx, worker_rx) = mpsc::channel();\n\n    /*\n     * Set a custom panic handler.\n     * This ensures that when any thread panics, the default panic handler will be invoked\n     * _and_ the entire process will exit with an error code.\n     */\n    let default_panic_handler = panic::take_hook();\n    panic::set_hook(Box::new(move |panic_info| {\n        default_panic_handler(panic_info);\n        process::exit(-1);\n    }));\n\n    debug!(\n        \"Queuing initial target file: {}\",\n        binwalker.base_target_file\n    );\n\n    // Queue the initial file path\n    target_files.insert(target_files.len(), binwalker.base_target_file.clone());\n\n    /*\n     * Main loop.\n     * Loop until all pending thread jobs are complete and there are no more files in the queue.\n     */\n    while !target_files.is_empty() || workers.active_count() > 0 {\n        // If there are files waiting to be analyzed and there is at least one free thread in the pool\n        if !target_files.is_empty() && workers.active_count() < workers.max_count() {\n            // Get the next file path from the target_files queue\n            let target_file = target_files\n                .pop_front()\n                .expect(\"Failed to retrieve next file from the queue\");\n\n            // Spawn a new worker for the new file\n            spawn_worker(\n                &workers,\n                binwalker.clone(),\n                target_file,\n                cliargs.stdin && file_count == 0,\n                cliargs.extract,\n                cliargs.carve,\n                worker_tx.clone(),\n            );\n        }\n\n        // Don't spin CPU cycles if there is no backlog of files to analyze\n        if target_files.is_empty() {\n            let sleep_time = time::Duration::from_millis(1);\n            thread::sleep(sleep_time);\n        }\n\n        // Some debug info on analysis progress\n        if last_progress_interval.elapsed().as_secs() >= PROGRESS_INTERVAL {\n            info!(\n                \"Status: active worker threads: {}/{}, files waiting in queue: {}\",\n                workers.active_count(),\n                workers.max_count(),\n                target_files.len()\n            );\n            last_progress_interval = time::Instant::now();\n        }\n\n        // Get response from a worker thread, if any\n        if let Ok(results) = worker_rx.try_recv() {\n            // Keep a tally of how many files have been analyzed\n            file_count += 1;\n\n            // Log analysis results to JSON file\n            json_logger.log(json::JSONType::Analysis(results.clone()));\n\n            // Nothing found? Nothing else to do for this file.\n            if results.file_map.is_empty() {\n                debug!(\"Found no results for file {}\", results.file_path);\n                continue;\n            }\n\n            // Print analysis results to screen\n            if should_display(&results, file_count, cliargs.verbose) {\n                display::print_analysis_results(cliargs.quiet, cliargs.extract, &results);\n            }\n\n            // If running recursively, add extraction results to list of files to analyze\n            if cliargs.matryoshka {\n                for (_signature_id, extraction_result) in results.extractions.into_iter() {\n                    if !extraction_result.do_not_recurse {\n                        for file_path in extractors::common::get_extracted_files(\n                            &extraction_result.output_directory,\n                        ) {\n                            debug!(\"Queuing {file_path} for analysis\");\n                            target_files.insert(target_files.len(), file_path.clone());\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    json_logger.close();\n\n    // If BINWALK_RM_SYMLINK env var was set, delete the base_target_file symlink\n    if (cliargs.carve || cliargs.extract) && std::env::var(BINWALK_RM_SYMLINK).is_ok() {\n        if let Err(e) = std::fs::remove_file(&binwalker.base_target_file) {\n            error!(\n                \"Request to remove extraction symlink file {} failed: {}\",\n                binwalker.base_target_file, e\n            );\n        }\n    }\n\n    // All done, show some basic statistics\n    display::print_stats(\n        cliargs.quiet,\n        run_time,\n        file_count,\n        binwalker.signature_count,\n        binwalker.pattern_count,\n    );\n\n    ExitCode::SUCCESS\n}\n\n/// Returns true if the specified results should be displayed to screen\nfn should_display(results: &AnalysisResults, file_count: usize, verbose: bool) -> bool {\n    let mut display_results: bool = false;\n\n    /*\n     * For brevity, when analyzing more than one file only display subsequent files whose results\n     * contain signatures that we always want displayed, or which contain extractable signatures.\n     * This can be overridden with the --verbose command line flag.\n     */\n    if file_count == 1 || verbose || !results.extractions.is_empty() {\n        display_results = true;\n    } else {\n        for signature in &results.file_map {\n            if signature.always_display {\n                display_results = true;\n                break;\n            }\n        }\n    }\n\n    display_results\n}\n\n/// Spawn a worker thread to analyze a file\nfn spawn_worker(\n    pool: &ThreadPool,\n    bw: binwalk::Binwalk,\n    target_file: String,\n    stdin: bool,\n    do_extraction: bool,\n    do_carve: bool,\n    worker_tx: mpsc::Sender<AnalysisResults>,\n) {\n    pool.execute(move || {\n        // Read in file data\n        let file_data = match common::read_input(&target_file, stdin) {\n            Err(_) => {\n                error!(\"Failed to read {target_file} data\");\n                b\"\".to_vec()\n            }\n            Ok(data) => data,\n        };\n\n        // Analyze target file, with extraction, if specified\n        let results = bw.analyze_buf(&file_data, &target_file, do_extraction);\n\n        // If data carving was requested as part of extraction, carve analysis results to disk\n        if do_carve {\n            let carve_count = carve_file_map(&file_data, &results);\n            info!(\"Carved {carve_count} data blocks to disk from {target_file}\");\n        }\n\n        // Report file results back to main thread\n        if let Err(e) = worker_tx.send(results) {\n            panic!(\n                \"Worker thread for {target_file} failed to send results back to main thread: {e}\"\n            );\n        }\n    });\n}\n\n/// Carve signatures identified during analysis to separate files on disk.\n/// Returns the number of carved files created.\n/// Note that unknown blocks of file data are also carved to disk, so the number of files\n/// created may be larger than the number of results defined in results.file_map.\nfn carve_file_map(file_data: &[u8], results: &binwalk::AnalysisResults) -> usize {\n    let mut carve_count: usize = 0;\n    let mut last_known_offset: usize = 0;\n    let mut unknown_bytes: Vec<(usize, usize)> = Vec::new();\n\n    // No results, don't do anything\n    if !results.file_map.is_empty() {\n        // Loop through all identified signatures in the file\n        for signature_result in &results.file_map {\n            // If there is data between the last signature and this signature, it is some chunk of unknown data\n            if signature_result.offset > last_known_offset {\n                unknown_bytes.push((\n                    last_known_offset,\n                    signature_result.offset - last_known_offset,\n                ));\n            }\n\n            // Carve this signature's data to disk\n            if carve_file_data_to_disk(\n                &results.file_path,\n                file_data,\n                &signature_result.name,\n                signature_result.offset,\n                signature_result.size,\n            ) {\n                carve_count += 1;\n            }\n\n            // Update the last known offset to the end of this signature's data\n            last_known_offset = signature_result.offset + signature_result.size;\n        }\n\n        // Calculate the size of any remaining data from the end of the last signature to EOF\n        let remaining_data = file_data.len() - last_known_offset;\n\n        // Add any remaining unknown data to the unknown_bytes list\n        if remaining_data > 0 {\n            unknown_bytes.push((last_known_offset, remaining_data));\n        }\n\n        // All known signature data has been carved to disk, now carve any unknown blocks of data to disk\n        for (offset, size) in unknown_bytes {\n            if carve_file_data_to_disk(&results.file_path, file_data, \"unknown\", offset, size) {\n                carve_count += 1;\n            }\n        }\n    }\n\n    carve_count\n}\n\n/// Carves a block of file data to a new file on disk\nfn carve_file_data_to_disk(\n    source_file_path: &str,\n    file_data: &[u8],\n    name: &str,\n    offset: usize,\n    size: usize,\n) -> bool {\n    let chroot = extractors::common::Chroot::new(None);\n\n    // Carved file path will be: <source file path>_<offset>_<name>.raw\n    let carved_file_path = format!(\"{source_file_path}_{offset}_{name}.raw\",);\n\n    debug!(\"Carving {carved_file_path}\");\n\n    // Carve the data to disk\n    if !chroot.carve_file(&carved_file_path, file_data, offset, size) {\n        error!(\n            \"Failed to carve {} [{:#X}..{:#X}] to disk\",\n            carved_file_path,\n            offset,\n            offset + size,\n        );\n        return false;\n    }\n\n    true\n}\n"
  },
  {
    "path": "src/signatures/aes.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_LOW, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION_AES_SBOX: &str = \"AES S-Box\";\npub const DESCRIPTION_AES_FT: &str = \"AES Forward Table\";\npub const DESCRIPTION_AES_RT: &str = \"AES Reverse Table\";\npub const DESCRIPTION_AES_RCON: &str = \"AES RCON\";\npub const DESCRIPTION_AES_ACC: &str = \"AES Acceleration Table\";\n\n/// AES S-box magic bytes\npub fn aes_sbox_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x63\\x7C\\x77\\x7B\\xF2\\x6B\\x6F\\xC5\".to_vec(), // Forward S-Box\n        b\"\\x52\\x09\\x6A\\xD5\\x30\\x36\\xA5\\x38\".to_vec(), // Reverse S-Box\n    ]\n}\n\npub fn aes_forward_table_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xC6\\x63\\x63\\xA5\\xF8\\x7C\\x7C\\x84\\xEE\\x77\\x77\\x99\\xF6\\x7B\\x7B\\x8D\".to_vec()]\n}\n\npub fn aes_reverse_table_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x51\\xF4\\xA7\\x50\\x7E\\x41\\x65\\x53\\x1A\\x17\\xA4\\xC3\\x3A\\x27\\x5E\\x96\".to_vec()]\n}\n\npub fn aes_rcon_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x01\\x02\\x04\\x08\\x10\\x20\\x40\\x80\\x1B\\x36\".to_vec(),\n        b\"\\x01\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\x08\\x00\\x00\\x00\\x10\\x00\\x00\\x00\\x20\\x00\\x00\\x00\\x40\\x00\\x00\\x00\\x80\\x00\\x00\\x00\\x1B\\x00\\x00\\x00\\x36\\x00\\x00\\x00\".to_vec(),\n    ]\n}\n\npub fn aes_acceleration_table_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\xA5\\x84\\x99\\x8D\\x0D\\xBD\\xB1\\x54\\x50\\x03\\xA9\\x7D\\x19\\x62\\xE6\\x9A\".to_vec(), // combined sbox x2\n        b\"\\xC6\\xF8\\xEE\\xF6\\xFF\\xD6\\xDE\\x91\\x60\\x02\\xCE\\x56\\xE7\\xB5\\x4D\\xEC\".to_vec(), // combined sbox x3\n        b\"\\x00\\x02\\x04\\x06\\x08\\x0a\\x0c\\x0e\\x10\\x12\\x14\\x16\\x18\\x1a\\x1c\\x1e\\x20\\x22\\x24\\x26\\x28\\x2a\\x2c\\x2e\".to_vec(),   // Gallois mult 2\n        b\"\\x00\\x03\\x06\\x05\\x0c\\x0f\\x0a\\x09\\x18\\x1b\\x1e\\x1d\\x14\\x17\\x12\\x11\\x30\\x33\\x36\\x35\\x3c\\x3f\\x3a\\x39\".to_vec(),   // Gallois mult 3\n        b\"\\x00\\x09\\x12\\x1b\\x24\\x2d\\x36\\x3f\\x48\\x41\\x5a\\x53\\x6c\\x65\\x7e\\x77\\x90\\x99\\x82\\x8b\\xb4\\xbd\\xa6\\xaf\".to_vec(),   // Gallois mult 9\n        b\"\\x00\\x0b\\x16\\x1d\\x2c\\x27\\x3a\\x31\\x58\\x53\\x4e\\x45\\x74\\x7f\\x62\\x69\\xb0\\xbb\\xa6\\xad\\x9c\\x97\\x8a\\x81\".to_vec(),   // Gallois mult 11\n        b\"\\x00\\x0d\\x1a\\x17\\x34\\x39\\x2e\\x23\\x68\\x65\\x72\\x7f\\x5c\\x51\\x46\\x4b\\xd0\\xdd\\xca\\xc7\\xe4\\xe9\\xfe\\xf3\".to_vec(),   // Gallois mult 13\n        b\"\\x00\\x0e\\x1c\\x12\\x38\\x36\\x24\\x2a\\x70\\x7e\\x6c\\x62\\x48\\x46\\x54\\x5a\\xe0\\xee\\xfc\\xf2\\xd8\\xd6\\xc4\\xca\".to_vec(),   // Gallois mult 14\n    ]\n}\n\n/// Validates the AES S-Box\npub fn aes_sbox_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let result = SignatureResult {\n        offset,\n        description: DESCRIPTION_AES_SBOX.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Nothing to do, just return success\n    Ok(result)\n}\n\n/// Validates the AES Forward Table\npub fn aes_forward_table_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let result = SignatureResult {\n        offset,\n        description: DESCRIPTION_AES_FT.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Nothing to do, just return success\n    Ok(result)\n}\n\n/// Validates the AES Reverse Table\npub fn aes_reverse_table_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let result = SignatureResult {\n        offset,\n        description: DESCRIPTION_AES_RT.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Nothing to do, just return success\n    Ok(result)\n}\n\n/// Validates the AES Acceleration Table\npub fn aes_acceleration_table_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let result = SignatureResult {\n        offset,\n        description: DESCRIPTION_AES_ACC.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Nothing to do, just return success\n    Ok(result)\n}\n\n/// Validates the AES RCON\npub fn aes_rcon_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let result = SignatureResult {\n        offset,\n        description: \"AES RCON\".to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Nothing to do, just return success\n    Ok(result)\n}\n"
  },
  {
    "path": "src/signatures/android_bootimg.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::android_bootimg::parse_android_bootimg_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Android boot image\";\n\n/// Android boot images always start with these bytes\npub fn android_bootimg_magic() -> Vec<Vec<u8>> {\n    vec![b\"ANDROID!\".to_vec()]\n}\n\n/// Validates the android boot image header\npub fn android_bootimg_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    if let Ok(bootimg_header) = parse_android_bootimg_header(&file_data[offset..]) {\n        if offset == 0 {\n            result.confidence = CONFIDENCE_MEDIUM;\n        }\n\n        result.description = format!(\n            \"{}, kernel size: {} bytes, kernel load address: {:#X}, ramdisk size: {} bytes, ramdisk load address: {:#X}\",\n            result.description,\n            bootimg_header.kernel_size,\n            bootimg_header.kernel_load_address,\n            bootimg_header.ramdisk_size,\n            bootimg_header.ramdisk_load_address,\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/androidsparse.rs",
    "content": "use crate::extractors::androidsparse::extract_android_sparse;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::androidsparse::parse_android_sparse_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Android sparse image\";\n\n/// Magic bytes for Android Sparse files\npub fn android_sparse_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x3A\\xFF\\x26\\xED\".to_vec()]\n}\n\n/// Parses Android Sparse files\npub fn android_sparse_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Default result, returned on success\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do a dry-run extraction\n    let dry_run = extract_android_sparse(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(total_size) = dry_run.size {\n            // Dry-run went OK, parse the header to get some useful info to report\n            if let Ok(header) = parse_android_sparse_header(&file_data[offset..]) {\n                // Update reported size and description\n                result.size = total_size;\n                result.description = format!(\n                    \"{}, version {}.{}, header size: {}, block size: {}, chunk count: {}, total size: {} bytes\",\n                    result.description,\n                    header.major_version,\n                    header.minor_version,\n                    header.header_size,\n                    header.block_size,\n                    header.chunk_count,\n                    total_size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/apfs.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::apfs::{MAGIC_OFFSET, parse_apfs_header};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"APple File System\";\n\n/// APFS magic bytes\npub fn apfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"NXSB\".to_vec()]\n}\n\n/// Validates the APFS header\npub fn apfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const MBR_BLOCK_SIZE: usize = 512;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if offset >= MAGIC_OFFSET {\n        result.offset = offset - MAGIC_OFFSET;\n        let available_data = file_data.len() - result.offset;\n\n        if let Ok(apfs_header) = parse_apfs_header(&file_data[result.offset..]) {\n            let mut truncated_message = \"\".to_string();\n            result.size = apfs_header.block_count * apfs_header.block_size;\n\n            // It is observed that an APFS contained in an EFIGPT with a protective MBR includes the MBR block in its size.\n            // If the APFS image is pulled out of the EFIGPT, the reported size will be 512 bytes too long, but otherwise valid.\n            if result.size > available_data {\n                let truncated_size = result.size - available_data;\n\n                // If the calculated size is 512 bytes short, adjust the reported APFS size accordingly\n                if truncated_size == MBR_BLOCK_SIZE {\n                    result.size -= truncated_size;\n                    truncated_message = format!(\" (truncated by {truncated_size} bytes)\");\n                }\n            }\n\n            result.description = format!(\n                \"{}, block size: {} bytes, block count: {}, total size: {} bytes{}\",\n                result.description,\n                apfs_header.block_size,\n                apfs_header.block_count,\n                result.size,\n                truncated_message\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/arcadyan.rs",
    "content": "use crate::extractors::arcadyan::extract_obfuscated_lzma;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Arcadyan obfuscated LZMA\";\n\n/// Obfuscated Arcadyan LZMA magic bytes\npub fn obfuscated_lzma_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x00\\xD5\\x08\\x00\".to_vec()]\n}\n\n/// Parses obfuscated Arcadyan LZMA data\npub fn obfuscated_lzma_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Magic bytes are 0x68 bytes into the actual file\n    const MAGIC_OFFSET: usize = 0x68;\n\n    // Success return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Sanity check on the reported offset; must be at least MAGIC_OFFSET bytes into the file\n    if offset >= MAGIC_OFFSET {\n        // Actual start of the Arcadyan data in the file\n        let start_offset: usize = offset - MAGIC_OFFSET;\n\n        // Do an extraction dry-run\n        let dry_run = extract_obfuscated_lzma(file_data, start_offset, None);\n\n        // If dry-run was successful, return success\n        if dry_run.success {\n            // Report the actual start of file data\n            result.offset = start_offset;\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/arj.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::arj::parse_arj_header;\n\npub const DESCRIPTION: &str = \"ARJ archive data\";\npub fn arj_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x60\\xea\".to_vec()]\n}\n\npub fn arj_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    if let Ok(arj_header) = parse_arj_header(&file_data[offset..]) {\n        let available_data = file_data.len() - offset;\n        // Sanity check the reported ARJ header size\n        if arj_header.header_size <= available_data {\n            // Return success\n            return Ok(SignatureResult {\n                description: format!(\n                    \"{}, header size: {}, version {}, minimum version to extract: {}, flags: {}, compression method: {}, file type: {}, original name: {}, original file date: {}, compressed file size: {}, uncompressed file size: {}, os: {}\",\n                    DESCRIPTION,\n                    arj_header.header_size,\n                    arj_header.version,\n                    arj_header.min_version,\n                    arj_header.flags,\n                    arj_header.compression_method,\n                    arj_header.file_type,\n                    arj_header.original_name,\n                    arj_header.original_file_date,\n                    arj_header.compressed_file_size,\n                    arj_header.uncompressed_file_size,\n                    arj_header.host_os,\n                ),\n                offset,\n                size: arj_header.header_size,\n                confidence: CONFIDENCE_MEDIUM,\n                extraction_declined: arj_header.file_type != *\"comment header\",\n                ..Default::default()\n            });\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/autel.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::autel::parse_autel_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Autel obfuscated firmware\";\n\n/// Autel magic bytes\npub fn autel_magic() -> Vec<Vec<u8>> {\n    vec![b\"ECC0101\\x00\".to_vec()]\n}\n\n/// Validates the Autel header\npub fn autel_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(autel_header) = parse_autel_header(&file_data[offset..]) {\n        result.size = autel_header.header_size + autel_header.data_size;\n        result.description = format!(\n            \"{}, header size: {} bytes, data size: {}, total size: {}\",\n            result.description, autel_header.header_size, autel_header.data_size, result.size\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/binhdr.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::binhdr::parse_bin_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"BIN firmware header\";\n\n/// BIN header magic bytes\npub fn bin_hdr_magic() -> Vec<Vec<u8>> {\n    vec![b\"U2ND\".to_vec()]\n}\n\n/// Validates the BIN header\npub fn bin_hdr_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const MAGIC_OFFSET: usize = 14;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if offset >= MAGIC_OFFSET {\n        result.offset = offset - MAGIC_OFFSET;\n\n        if let Ok(bin_header) = parse_bin_header(&file_data[result.offset..]) {\n            result.description = format!(\n                \"{}, board ID: {}, hardware revision: {}, firmware version: {}.{}\",\n                result.description,\n                bin_header.board_id,\n                bin_header.hardware_revision,\n                bin_header.firmware_version_major,\n                bin_header.firmware_version_minor,\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/bmp.rs",
    "content": "use crate::extractors::bmp::extract_bmp_image;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"BMP image (Bitmap)\";\n\n// BMPs start with these bytes\n// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader\n// \"The file type; must be 0x4d42 (the ASCII string \"BM\")\"\npub fn bmp_magic() -> Vec<Vec<u8>> {\n    vec![b\"BM\".to_vec()]\n}\n\n// Validates BMP header\npub fn bmp_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        name: \"bmp\".to_string(),\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Extraction dry-run to validate the image\n    let dry_run = extract_bmp_image(file_data, offset, None);\n\n    // If it was successful, inform the user\n    if dry_run.success {\n        // Retrieve total file size and report it to the user\n        if let Some(total_size) = dry_run.size {\n            result.description = format!(\"BMP image, total size: {total_size}\");\n            result.size = total_size;\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/btrfs.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::btrfs::parse_btrfs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"BTRFS file system\";\n\n/// BTRFS magic bytes\npub fn btrfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"_BHRfS_M\".to_vec()]\n}\n\n/// Validates the BTRFS header\npub fn btrfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Offset of the superblock magic bytes in a BTRFS image\n    const MAGIC_OFFSET: usize = 0x10040;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Sanity check the reported offset\n    if offset >= MAGIC_OFFSET {\n        // Actual offset is the location of the magic bytes minus the magic byte offset\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Parse the superblock header; this also validates the superblock CRC\n        if let Ok(btrfs_header) = parse_btrfs_header(&file_data[result.offset..]) {\n            result.size = btrfs_header.total_size;\n            result.description = format!(\n                \"{}, node size: {}, sector size: {}, leaf size: {}, stripe size: {}, bytes used: {}, total size: {} bytes\",\n                result.description,\n                btrfs_header.node_size,\n                btrfs_header.sector_size,\n                btrfs_header.leaf_size,\n                btrfs_header.stripe_size,\n                btrfs_header.bytes_used,\n                result.size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/bzip2.rs",
    "content": "use crate::extractors::bzip2::bzip2_decompressor;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"bzip2 compressed data\";\n\n/// Bzip2 magic bytes; includes the magic bytes, version number, block size, and compressed magic signature\npub fn bzip2_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"BZh91AY&SY\".to_vec(),\n        b\"BZh81AY&SY\".to_vec(),\n        b\"BZh71AY&SY\".to_vec(),\n        b\"BZh61AY&SY\".to_vec(),\n        b\"BZh51AY&SY\".to_vec(),\n        b\"BZh41AY&SY\".to_vec(),\n        b\"BZh31AY&SY\".to_vec(),\n        b\"BZh21AY&SY\".to_vec(),\n        b\"BZh11AY&SY\".to_vec(),\n    ]\n}\n\n/// Bzip2 header parser\npub fn bzip2_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let dry_run = bzip2_decompressor(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(bzip2_size) = dry_run.size {\n            result.size = bzip2_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/cab.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::cab::parse_cab_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Microsoft Cabinet archive\";\n\n/// CAB magic bytes; includes the magic bytes and the following reserved1 header entry, which must be 0.\npub fn cab_magic() -> Vec<Vec<u8>> {\n    vec![b\"MSCF\\x00\\x00\\x00\\x00\".to_vec()]\n}\n\n/// Parses and cabinet file signature\npub fn cab_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Parse the CAB header\n    if let Ok(cab_header) = parse_cab_header(&file_data[offset..]) {\n        let available_data = file_data.len() - offset;\n\n        // Sanity check the reported CAB file size\n        if cab_header.total_size <= available_data {\n            // Return success\n            return Ok(SignatureResult {\n                description: format!(\n                    \"{}, file count: {}, folder count: {}, header size: {}, total size: {} bytes\",\n                    DESCRIPTION,\n                    cab_header.file_count,\n                    cab_header.folder_count,\n                    cab_header.header_size,\n                    cab_header.total_size\n                ),\n                offset,\n                size: cab_header.total_size,\n                confidence: CONFIDENCE_MEDIUM,\n                ..Default::default()\n            });\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/cfe.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"CFE bootloader\";\n\n/// CFE bootloader always contains this string\npub fn cfe_magic() -> Vec<Vec<u8>> {\n    vec![b\"CFE1CFE1\".to_vec()]\n}\n\n/// Validate the CFE signature\npub fn cfe_parser(_file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Magic bytes occur this many bytes into the bootloader\n    const CFE_MAGIC_OFFSET: usize = 28;\n\n    // Success result; confidence is set to low by default as little additional validation is performed\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // CFE signature must start at least CFE_MAGIC_OFFSET bytes into the file\n    if offset >= CFE_MAGIC_OFFSET {\n        // Adjust the reported starting offset accordingly\n        result.offset = offset - CFE_MAGIC_OFFSET;\n\n        // If this signature occurs at the very beginning of a file, our confidence is a bit higher\n        if result.offset == 0 {\n            result.confidence = CONFIDENCE_MEDIUM;\n        }\n\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/chk.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::chk::parse_chk_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"CHK firmware header\";\n\n/// CHK firmware always start with these bytes\npub fn chk_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x2A\\x23\\x24\\x5E\".to_vec()]\n}\n\n/// Parse and validate CHK headers\npub fn chk_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Parse the CHK header\n    if let Ok(chk_header) = parse_chk_header(&file_data[offset..]) {\n        // Calculate reported image size and size of available data\n        let available_data: usize = file_data.len() - offset;\n        let image_total_size: usize =\n            chk_header.header_size + chk_header.kernel_size + chk_header.rootfs_size;\n\n        // Total reported image size should be between the header size and the file size\n        if available_data >= image_total_size && image_total_size > chk_header.header_size {\n            // Report the size of the header and a brief description\n            result.size = chk_header.header_size;\n            result.description = format!(\n                \"{}, board ID: {}, header size: {} bytes, data size: {} bytes\",\n                result.description,\n                chk_header.board_id,\n                chk_header.header_size,\n                chk_header.kernel_size + chk_header.rootfs_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/common.rs",
    "content": "use crate::extractors;\nuse serde::{Deserialize, Serialize};\n\n/// Some pre-defined confidence levels for SignatureResult structures\npub const CONFIDENCE_LOW: u8 = 0;\npub const CONFIDENCE_MEDIUM: u8 = 128;\npub const CONFIDENCE_HIGH: u8 = 250;\n\n/// Return value of SignatureParser upon error\n#[derive(Debug, Clone)]\npub struct SignatureError;\n\n/// Type definition for signature parser functions\n///\n/// ## Arguments\n///\n/// All signature parsers are passed two arguments: a vector of u8 bytes, and an offset into that vector where the signature's magic bytes were found.\n///\n/// ## Return values\n///\n/// Each signature parser is responsible for parsing and validating signature candidates.\n///\n/// They must return either a SignatureResult struct if validation succeeds, or a SignatureError if validation fails.\npub type SignatureParser = fn(&[u8], usize) -> Result<SignatureResult, SignatureError>;\n\n/// Describes a valid identified file signature\n///\n/// ## Construction\n///\n/// The SignatureResult struct is returned by all SignatureParser functions upon success.\n///\n/// The `id`, `name`, and `always_display` fields are automatically populated after being returned by a SignatureParser function, and need not be set by the SignatureParser function.\n///\n/// At the very least, SignatureParser functions should define the `offset` and `description` fields.\n///\n/// ## Additional Notes\n///\n/// If a SignatureResult contains a `size` of `0` (the default value), it is assumed to extend to the beginning of the next signature, or EOF, whichever comes first.\n///\n/// SignatureResult structs are sortable by `offset`.\n///\n/// SignatureResult structs can be JSON serialized/deserialized with [serde](https://crates.io/crates/serde).\n#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]\npub struct SignatureResult {\n    /// File/data offset where this signature starts\n    pub offset: usize,\n    /// A UUID uniquely identifying this signature result; auto-populated\n    pub id: String,\n    /// Size of the signature data, 0 if unknown\n    pub size: usize,\n    /// A unique name for this signature type, auto-populated from the signature definition in Signature.name\n    pub name: String,\n    /// One of CONFIDENCE_LOW, CONFIDENCE_MEDIUM, CONFIDENCE_HIGH; default is CONFIDENCE_LOW\n    pub confidence: u8,\n    /// Human readable description of this signature\n    pub description: String,\n    /// If true, always display this signature result; auto-populated from the signature definition in Signature.always_display\n    pub always_display: bool,\n    /// Set to true to disable extraction for this particular signature result (default: false)\n    pub extraction_declined: bool,\n    /// Signatures may specify a preferred extractor, which overrides the default extractor specified in the Signature.extractor definition\n    #[serde(skip_deserializing, skip_serializing)]\n    pub preferred_extractor: Option<extractors::common::Extractor>,\n}\n\n/// Defines a file signature to search for, and how to extract that file type\n#[derive(Debug, Clone)]\npub struct Signature {\n    /// Unique name for the signature (no whitespace)\n    pub name: String,\n    /// Set to true if this is a short signature; it will only be matched at the beginning of a file\n    pub short: bool,\n    /// List of magic byte patterns associated with this signature\n    pub magic: Vec<Vec<u8>>,\n    /// Offset of magic bytes from the beginning of the file; only relevant for short signatures\n    pub magic_offset: usize,\n    /// Human readable description of this signature\n    pub description: String,\n    /// If true, will always display files that contain this signature, even during recursive extraction\n    pub always_display: bool,\n    /// Specifies the signature parser to invoke for magic match validation\n    pub parser: SignatureParser,\n    /// Specifies the extractor to use when extracting this file type\n    pub extractor: Option<extractors::common::Extractor>,\n}\n"
  },
  {
    "path": "src/signatures/compressd.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"compress'd data\";\n\n/// Compress'd files always start with these bytes\npub fn compressd_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x1F\\x9D\\x90\".to_vec()]\n}\n\n/// \"Validate\" the compress'd header\npub fn compressd_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value; confidence is medium since this only matches magic bytes at the beginning of a file\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    if offset == 0 {\n        result.confidence = CONFIDENCE_MEDIUM;\n    }\n\n    Ok(result)\n}\n"
  },
  {
    "path": "src/signatures/copyright.rs",
    "content": "use crate::common::get_cstring;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Copyright text\";\n\n/// Magic copyright strings to search for\npub fn copyright_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"copyright\".to_vec(),\n        b\"Copyright\".to_vec(),\n        b\"COPYRIGHT\".to_vec(),\n    ]\n}\n\n/// Parse copyright magic candidates\npub fn copyright_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Size of \"copright\" string\n    const MAGIC_SIZE: usize = 9;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Get a NULL terminated string, starting at the \"copright\" text\n    let copyright_string = get_cstring(&file_data[offset..]);\n\n    // Make sure we got more than just the \"copyright\" string\n    if copyright_string.len() > MAGIC_SIZE {\n        result.size = copyright_string.len();\n        // Truncate copright text to 100 bytes\n        result.description = format!(\"{}: \\\"{:.100}\\\"\", result.description, copyright_string);\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/cpio.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::cpio;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"CPIO ASCII archive\";\n\n/// Magic bytes for CPIO archives with and without CRC's\npub fn cpio_magic() -> Vec<Vec<u8>> {\n    vec![b\"070701\".to_vec(), b\"070702\".to_vec()]\n}\n\n/// Parse and validate CPIO archives\npub fn cpio_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // The last CPIO entry will have this file name\n    const EOF_MARKER: &str = \"TRAILER!!!\";\n\n    let mut header_count: usize = 0;\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let mut next_header_offset = offset;\n    let mut previous_header_offset = None;\n    let available_data = file_data.len();\n\n    // Loop over all the available data, or until CPIO EOF, or until error\n    while is_offset_safe(available_data, next_header_offset, previous_header_offset) {\n        // Get the CPIO entry's raw data\n        match file_data.get(next_header_offset..) {\n            None => {\n                break;\n            }\n            Some(cpio_entry_data) => {\n                // Parse this CPIO entry's header\n                match cpio::parse_cpio_entry_header(cpio_entry_data) {\n                    Err(_) => {\n                        break;\n                    }\n                    Ok(cpio_header) => {\n                        // Sanity check the magic bytes\n                        if !cpio_magic().contains(&cpio_header.magic) {\n                            break;\n                        }\n\n                        // Keep a tally of how many CPIO headers have been processed\n                        header_count += 1;\n\n                        // Update the total size of the CPIO file to include this header and its data\n                        result.size += cpio_header.header_size + cpio_header.data_size;\n\n                        // If EOF marker has been found, we're done\n                        if cpio_header.file_name == EOF_MARKER {\n                            // If one or fewer CPIO headers were found, consider it a false positive;\n                            // a CPIO archive should have at least one file/directory entry, and one EOF entry.\n                            if header_count > 1 {\n                                // Return the result; reported file count does not include the EOF entry\n                                result.description = format!(\n                                    \"{}, file count: {}\",\n                                    result.description,\n                                    header_count - 1\n                                );\n                                return Ok(result);\n                            }\n\n                            break;\n                        }\n\n                        // Update the previous and next header offset values for the next loop iteration\n                        previous_header_offset = Some(next_header_offset);\n                        next_header_offset = offset + result.size;\n                    }\n                }\n            }\n        }\n    }\n\n    // No EOF marker was found, or an error occurred in processing the CPIO headers\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/cramfs.rs",
    "content": "use crate::common;\nuse crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::cramfs::parse_cramfs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"CramFS filesystem\";\n\n/// This is technically the CramFS \"signature\", not the magic bytes, but it's endian-agnostic\npub fn cramfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"Compressed ROMFS\".to_vec()]\n}\n\n/// Parse and validate the CramFS header\npub fn cramfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Some constant relative offsets\n    const SIGNATURE_OFFSET: usize = 16;\n    const CRC_START_OFFSET: usize = 32;\n    const CRC_END_OFFSET: usize = 36;\n\n    let mut result = SignatureResult {\n        offset: offset - SIGNATURE_OFFSET,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    if let Some(cramfs_header_data) = file_data.get(result.offset..) {\n        // Parse the CramFS header; also validates that the reported size is greater than the header size\n        if let Ok(cramfs_header) = parse_cramfs_header(cramfs_header_data) {\n            // Update the reported size\n            result.size = cramfs_header.size;\n\n            if let Some(cramfs_image_data) =\n                file_data.get(result.offset..result.offset + result.size)\n            {\n                /*\n                 * Create a copy of the cramfs image; we have to NULL out the checksum field to calculate the CRC.\n                 * This typically shouldn't be too bad on performance, CramFS images are usually relatively small.\n                 */\n                let mut cramfs_image: Vec<u8> = cramfs_image_data.to_vec();\n\n                // Null out the checksum field\n                for crc_byte in cramfs_image\n                    .iter_mut()\n                    .take(CRC_END_OFFSET)\n                    .skip(CRC_START_OFFSET)\n                {\n                    *crc_byte = 0;\n                }\n\n                // For displaying an error message in the description\n                let mut error_message: &str = \"\";\n\n                // On CRC error, lower confidence and report the checksum error\n                // (have seen partially corrupted images that still extract Ok)\n                if common::crc32(&cramfs_image) != cramfs_header.checksum {\n                    error_message = \" (checksum error)\";\n                    result.confidence = CONFIDENCE_MEDIUM;\n                }\n\n                result.description = format!(\n                    \"{}, {} endian, {} files, total size: {} bytes{}\",\n                    result.description,\n                    cramfs_header.endianness,\n                    cramfs_header.file_count,\n                    cramfs_header.size,\n                    error_message\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/csman.rs",
    "content": "use crate::extractors::csman::extract_csman_dat;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"CSman DAT file\";\n\n/// CSMAN DAT files always start with these bytes\npub fn csman_magic() -> Vec<Vec<u8>> {\n    // Big and little endian magic\n    vec![b\"SC\".to_vec(), b\"CS\".to_vec()]\n}\n\n/// Validates the CSMAN DAT file\npub fn csman_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let dry_run = extract_csman_dat(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(total_size) = dry_run.size {\n            result.size = total_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dahua_zip.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\nuse crate::signatures::zip;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Dahua ZIP archive\";\n\n/// Dahua ZIP file entry magic bytes\npub fn dahua_zip_magic() -> Vec<Vec<u8>> {\n    // The first ZIP file entry in the Dahua ZIP file is has \"DH\" instead of \"PK\".\n    // Otherwise, it is a normal ZIP file.\n    vec![b\"DH\\x03\\x04\".to_vec()]\n}\n\n/// Validates a Dahua ZIP file entry signature\npub fn dahua_zip_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Parse & validate the Dahua ZIP file like a normal ZIP file\n    if let Ok(mut result) = zip::zip_parser(file_data, offset) {\n        // Replace the normal ZIP description string with our description string\n        result.description = result.description.replace(zip::DESCRIPTION, DESCRIPTION);\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/deb.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::deb::parse_deb_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Debian package file\";\n\n/// Debian archives always start with these bytes\npub fn deb_magic() -> Vec<Vec<u8>> {\n    vec![b\"!<arch>\\ndebian-binary\\x20\\x20\\x20\".to_vec()]\n}\n\n/// Validates debian archive signatures\npub fn deb_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the deb header\n    if let Ok(deb_header) = parse_deb_header(&file_data[offset..]) {\n        result.size = deb_header.file_size;\n\n        // Make sure the reported size of the DEB file is sane\n        if result.size <= (file_data.len() - offset) {\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dkbs.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::dkbs::parse_dkbs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DKBS firmware header\";\n\n/// DKBS firmware magic\npub fn dkbs_magic() -> Vec<Vec<u8>> {\n    vec![b\"_dkbs_\".to_vec()]\n}\n\n/// Validates the DKBS header\npub fn dkbs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const MAGIC_OFFSET: usize = 7;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Sanity check the magic bytes offset\n    if offset >= MAGIC_OFFSET {\n        // Magic bytes occur 7 bytes into the actual firmware header\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Parse the firmware header\n        if let Ok(dkbs_header) = parse_dkbs_header(&file_data[result.offset..]) {\n            // Calculate the total bytes available after the firmware header\n            let available_data: usize = file_data.len() - result.offset;\n\n            // Sanity check on the total reported DKBS firmware size\n            if available_data >= (dkbs_header.header_size + dkbs_header.data_size) {\n                // If this header starts at the beginning of the file, confidence is high\n                if result.offset == 0 {\n                    result.confidence = CONFIDENCE_HIGH;\n                }\n\n                // Report header size and description\n                result.size = dkbs_header.header_size;\n                result.description = format!(\n                    \"{}, board ID: {}, firmware version: {}, boot device: {}, endianness: {}, header size: {} bytes, data size: {}\",\n                    result.description,\n                    dkbs_header.board_id,\n                    dkbs_header.version,\n                    dkbs_header.boot_device,\n                    dkbs_header.endianness,\n                    dkbs_header.header_size,\n                    dkbs_header.data_size\n                );\n\n                // Return OK\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dlink_tlv.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::signatures::openssl::openssl_crypt_parser;\nuse crate::structures::dlink_tlv::parse_dlink_tlv_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"D-Link TLV firmware\";\n\n/// TLV firmware images always start with these bytes\npub fn dlink_tlv_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x64\\x80\\x19\\x40\".to_vec()]\n}\n\n/// Validates the TLV header\npub fn dlink_tlv_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Checksum calculation includes the 8-byte header that preceeds the actual payload data\n    const CHECKSUM_OFFSET: usize = 8;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(tlv_header) = parse_dlink_tlv_header(&file_data[offset..]) {\n        // Calculate the start and end offsets for the payload data over which the checksum is calculated\n        let data_start = offset + tlv_header.header_size - CHECKSUM_OFFSET;\n        let data_end = data_start + tlv_header.data_size + CHECKSUM_OFFSET;\n\n        // Get the payload data and calculate the MD5 hash\n        if let Some(payload_data) = file_data.get(data_start..data_end) {\n            let payload_md5 = format!(\"{:x}\", md5::compute(payload_data));\n\n            // If the MD5 checksum exists, make sure it matches\n            if tlv_header.data_checksum.is_empty() || payload_md5 == tlv_header.data_checksum {\n                result.size = tlv_header.header_size + tlv_header.data_size;\n                result.description = format!(\n                    \"{}, model name: {}, board ID: {}, header size: {} bytes, data size: {} bytes\",\n                    result.description,\n                    tlv_header.model_name,\n                    tlv_header.board_id,\n                    tlv_header.header_size,\n                    tlv_header.data_size,\n                );\n\n                // Check if the firmware data is OpenSSL encrypted\n                if let Some(crypt_data) = file_data.get(offset + tlv_header.header_size..) {\n                    if let Ok(openssl_signature) = openssl_crypt_parser(crypt_data, 0) {\n                        result.description =\n                            format!(\"{}, {}\", result.description, openssl_signature.description);\n                    }\n                }\n\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dlke.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::jboot::parse_jboot_arm_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DLK encrypted firmware\";\n\n/// DLKE encrypted firmware images always start with these bytes\npub fn dlke_magic() -> Vec<Vec<u8>> {\n    // These magic bytes are technically the ROM-ID field of a JBOOT header\n    vec![b\"DLK6E8202001\".to_vec(), b\"DLK6E6110002\".to_vec()]\n}\n\n/// Validates the DLKE header\npub fn dlke_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the first header, which describes the size of the firmware signature\n    if let Ok(dlke_signature_header) = parse_jboot_arm_header(&file_data[offset..]) {\n        // Second header should immediately follow the first\n        if let Some(dlke_crypt_header_data) = file_data\n            .get(offset + dlke_signature_header.header_size + dlke_signature_header.data_size..)\n        {\n            // Parse the second header, which describes the size of the encrypted data\n            if let Ok(dlke_crypt_header) = parse_jboot_arm_header(dlke_crypt_header_data) {\n                result.size = dlke_signature_header.header_size\n                    + dlke_signature_header.data_size\n                    + dlke_crypt_header.header_size\n                    + dlke_crypt_header.data_size;\n                result.description = format!(\n                    \"{}, signature size: {} bytes, encrypted data size: {} bytes\",\n                    result.description,\n                    dlke_signature_header.data_size,\n                    dlke_crypt_header.data_size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dlob.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::dlob::parse_dlob_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DLOB firmware header\";\n\n/// DLOB firmware images always start with these bytes\npub fn dlob_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x5e\\xa3\\xa4\\x17\".to_vec()]\n}\n\n/// Validates the DLOB header\npub fn dlob_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    let available_data: usize = file_data.len() - offset;\n\n    if let Ok(dlob_header) = parse_dlob_header(&file_data[offset..]) {\n        // Sanity check on the total reported DLOB size\n        if available_data >= (dlob_header.header_size + dlob_header.data_size) {\n            // Don't skip the DLOB contents; it's mostly just a metadata header\n            result.size = dlob_header.header_size;\n            result.description = format!(\n                \"{}, header size: {} bytes, data size: {}\",\n                result.description, dlob_header.header_size, dlob_header.data_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dmg.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::dmg::parse_dmg_footer;\nuse aho_corasick::AhoCorasick;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Apple Disk iMaGe\";\n\n/// 4-byte magic, 4-byte version (v4), 4-byte structure size (0x0200).\n///  This is actually the magic bytes of the DMG footer, there is no standard header format.\npub fn dmg_magic() -> Vec<Vec<u8>> {\n    vec![b\"koly\\x00\\x00\\x00\\x04\\x00\\x00\\x02\\x00\".to_vec()]\n}\n\n/// Validates the DMG footer\npub fn dmg_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Confidence is set to HIGH + 1 to ensure this overrides other signatures.\n    // DMG's typically start with compressed data, and the file should be treated\n    // as a DMG, not just compressed data.\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH + 1,\n        ..Default::default()\n    };\n\n    // Parse the DMG footer\n    if let Ok(dmg_footer) = parse_dmg_footer(&file_data[offset..]) {\n        /*\n         * DMG files have the following layout:\n         *\n         *      [ image data ]  [ xml data ]  [ footer ]\n         *\n         * We can only signature reliably on the footer, which does contain the offsets and sizes of the image data and xml data.\n         * In theory, this would allow us to calculate the starting offset, and size, of the DMG image.\n         *\n         * In practice, signed DMG files have additional data between the XML and the footer. This extra data appears to\n         * be related to signing certificates and is variable in length, making the above theoretical calculations of the DMG offset\n         * and size invalid.\n         *\n         * Instead, we have to search the file data for the XML property, then the correct offset can be calculated.\n         */\n\n        // Make sure the length of image data and length of XML data are sane\n        if (dmg_footer.data_length + dmg_footer.xml_length) <= offset {\n            // Locate the XML data\n            if let Some(xml_offset) = find_xml_property_list(file_data) {\n                // Make sure the XML data comes after the image data\n                if xml_offset >= dmg_footer.data_length {\n                    // Report the result\n                    result.size = offset + dmg_footer.footer_size;\n                    result.offset = xml_offset - dmg_footer.data_length;\n                    result.description =\n                        format!(\"{}, total size: {} bytes\", result.description, result.size);\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\nfn find_xml_property_list(file_data: &[u8]) -> Option<usize> {\n    // XML data should start with this string\n    const XML_SIGNATURE: &str = \"<?xml\";\n    const MIN_XML_LENGTH: usize = 1024;\n    const BLKX_KEY: &str = \"<key>blkx</key>\";\n\n    let grep = AhoCorasick::new(vec![XML_SIGNATURE]).unwrap();\n\n    for xml_match in grep.find_overlapping_iter(file_data) {\n        let xml_start = xml_match.start();\n        let xml_end = xml_start + MIN_XML_LENGTH;\n\n        if let Some(xml_data) = file_data.get(xml_start..xml_end) {\n            if let Ok(xml_string) = String::from_utf8(xml_data.to_vec()) {\n                if xml_string.contains(BLKX_KEY) {\n                    return Some(xml_start);\n                }\n            }\n        }\n    }\n\n    None\n}\n"
  },
  {
    "path": "src/signatures/dms.rs",
    "content": "use crate::extractors::swapped::byte_swap;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::dms::parse_dms_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DMS firmware image\";\n\n/// DMS firmware image magic bytes\npub fn dms_magic() -> Vec<Vec<u8>> {\n    vec![b\"0><1\".to_vec()]\n}\n\n/// Validates the DMS header\npub fn dms_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const MIN_SIZE: usize = 0x100;\n    const BYTE_SWAP_SIZE: usize = 2;\n    const MAGIC_OFFSET: usize = 4;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // The magic bytes start at offset 4\n    if offset >= MAGIC_OFFSET {\n        result.offset = offset - MAGIC_OFFSET;\n\n        if let Some(dms_data) = file_data.get(result.offset..result.offset + MIN_SIZE) {\n            // DMS firmware images have every 2 bytes swapped\n            let swapped_data = byte_swap(dms_data, BYTE_SWAP_SIZE);\n\n            // Validate the DMS firmware header\n            if let Ok(dms_header) = parse_dms_header(&swapped_data) {\n                result.size = dms_header.image_size;\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dpapi.rs",
    "content": "use crate::{\n    signatures::common::{SignatureError, SignatureResult},\n    structures::dpapi::parse_dpapi_blob_header,\n};\n\nuse super::common::CONFIDENCE_MEDIUM;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DPAPI blob data\";\n\n/// DPAPI blob data header will always start with these bytes\npub fn dpapi_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x01\\x00\\x00\\x00\\xD0\\x8c\\x9d\\xdf\\x01\\x15\\xd1\\x11\\x8c\\x7a\\x00\\xc0\\x4f\\xc2\\x97\\xeb\"\n            .to_vec(),\n    ]\n}\n\n/// Returns success with additional details\npub fn dpapi_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(header) = parse_dpapi_blob_header(&file_data[offset..]) {\n        result.description = format!(\n            \"{}, header_size: {}, blob_size: {}, version: {}, provider_id: {}, master_key_version: {}, \n             master_key_id: {}, flags: {}, description_len: {}, crypto_algorithm: {}, crypti_alg_len: {}, \n             salt_len: {}, hmac_key_len: {}, hash_algorithm: {}, hash_alg_len: {}, hmac2_key_len: {}, \n             data_len: {}, sign_len: {}\", \n             result.description, header.header_size, header.blob_size, header.version, header.provider_id,\n             header.master_key_version, header.master_key_id, header.flags, header.description_len,\n             header.crypto_algorithm, header.crypti_alg_len, header.salt_len, header.hmac_key_len,\n             header.hash_algorithm, header.hash_alg_len, header.hmac2_key_len, header.data_len, header.sign_len\n            );\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dtb.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::dtb::parse_dtb_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Device tree blob (DTB)\";\n\n/// DTB files start with these magic bytes\npub fn dtb_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xD0\\x0D\\xFE\\xED\".to_vec()]\n}\n\n/// Validates the DTB header\npub fn dtb_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Sucessful result\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Parse the DTB header\n    if let Ok(dtb_header) = parse_dtb_header(&file_data[offset..]) {\n        // Calculate the offsets of where the dt_struct and dt_strings end\n        let dt_struct_end: usize = offset + dtb_header.struct_offset + dtb_header.struct_size;\n        let dt_strings_end: usize = offset + dtb_header.strings_offset + dtb_header.strings_size;\n\n        // Sanity check the dt_struct and dt_strings offsets\n        if file_data.len() >= dt_struct_end && file_data.len() >= dt_strings_end {\n            result.size = dtb_header.total_size;\n            result.description = format!(\n                \"{}, version: {}, CPU ID: {}, total size: {} bytes\",\n                result.description, dtb_header.version, dtb_header.cpu_id, result.size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/dxbc.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::dxbc::parse_dxbc_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DirectX shader bytecode\";\n\n/// DXBC file magic bytes\npub fn dxbc_magic() -> Vec<Vec<u8>> {\n    vec![b\"DXBC\".to_vec()]\n}\n\n/// Validates the DXBC header\npub fn dxbc_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const CHUNK_SM4: [u8; 4] = *b\"SHDR\";\n    const CHUNK_SM5: [u8; 4] = *b\"SHEX\";\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(header) = parse_dxbc_header(&file_data[offset..]) {\n        result.confidence = CONFIDENCE_HIGH;\n        result.size = header.size;\n\n        let shader_model = if header.chunk_ids.contains(&CHUNK_SM4) {\n            \"Shader Model 4\"\n        } else if header.chunk_ids.contains(&CHUNK_SM5) {\n            \"Shader Model 5\"\n        } else {\n            \"Unknown Shader Model\"\n        };\n\n        result.description = format!(\"{}, {}\", result.description, shader_model);\n\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/ecos.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_LOW, SignatureError, SignatureResult};\n\n/// Human readable description\npub const EXCEPTION_HANDLER_DESCRIPTION: &str = \"eCos kernel exception handler\";\n\n/// Big and little endian magic byte signatures for eCos kernel exception handlers (MIPS only)\npub fn exception_handler_magic() -> Vec<Vec<u8>> {\n    /*\n     * eCos kernel exception handlers\n     *\n     * mfc0    $k0, Cause       # Cause of last exception\n     * nop                      # Some versions of eCos omit the nop\n     * andi    $k0, 0x7F\n     * li      $k1, 0xXXXXXXXX\n     * add     $k1, $k0\n     * lw      $k1, 0($k1)\n     * jr      $k1\n     * nop\n     */\n    vec![\n        b\"\\x00\\x68\\x1A\\x40\\x00\\x00\\x00\\x00\\x7F\\x00\\x5A\\x33\".to_vec(),\n        b\"\\x00\\x68\\x1A\\x40\\x7F\\x00\\x5A\\x33\".to_vec(),\n        b\"\\x40\\x1A\\x68\\x00\\x00\\x00\\x00\\x00\\x33\\x5A\\x00\\x7F\".to_vec(),\n        b\"\\x40\\x1A\\x68\\x00\\x33\\x5A\\x00\\x7F\".to_vec(),\n    ]\n}\n\n/// Parses the eCos exception handler signature\npub fn exception_handler_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: EXCEPTION_HANDLER_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    let mut endianness: &str = \"big\";\n\n    // Detect endianess\n    if file_data[offset] == 0 {\n        endianness = \"little\";\n    }\n\n    result.description = format!(\"{}, MIPS {} endian\", result.description, endianness);\n    Ok(result)\n}\n"
  },
  {
    "path": "src/signatures/efigpt.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::efigpt::parse_efigpt_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"EFI Global Partition Table\";\n\n/// EFI GPT always contains these bytes\npub fn efigpt_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x55\\xAAEFI PART\".to_vec()]\n}\n\n/// Validates the EFI GPT header\npub fn efigpt_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Offset of magic bytes from the start of the MBR\n    const MAGIC_OFFSET: usize = 0x01FE;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let available_data = file_data.len() - offset;\n\n    if offset >= MAGIC_OFFSET {\n        // MBR actually starts this may bytes before the magic bytes\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Get the EFI data, including the MBR block\n        if let Some(efi_data) = file_data.get(result.offset..) {\n            // Parse the EFI data; this also validates CRC so if this succeeds, confidence is high\n            if let Ok(efi_header) = parse_efigpt_header(efi_data) {\n                // Some EFI images have been observed to define partitions that extend beyond EOF.\n                // If that is the case, assume the EFI image extends to EOF.\n                if efi_header.total_size > available_data {\n                    result.size = available_data;\n                } else {\n                    result.size = efi_header.total_size;\n                }\n                result.description = format!(\"{}, total size: {}\", result.description, result.size);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/elf.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::elf::parse_elf_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"ELF binary\";\n\n/// ELF files start with these bytes\npub fn elf_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x7FELF\".to_vec()]\n}\n\n/// Parse and validate the ELF header\npub fn elf_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful result\n    let mut result = SignatureResult {\n        offset,\n        name: \"elf\".to_string(),\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // If the header is parsed successfully, consider it valid\n    if let Ok(elf_header) = parse_elf_header(&file_data[offset..]) {\n        result.description = format!(\n            \"{}, {}-bit {}, {} for {}, {} endian\",\n            result.description,\n            elf_header.class,\n            elf_header.exe_type,\n            elf_header.machine,\n            elf_header.osabi,\n            elf_header.endianness\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/encfw.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse std::collections::HashMap;\n\n/// Known encrypted firmware magics and their associated make/model\nfn encfw_known_firmware() -> HashMap<Vec<u8>, String> {\n    HashMap::from([\n        (\n            b\"\\xdf\\x8c\\x39\\x0d\".to_vec(),\n            \"D-Link DIR-822 rev C\".to_string(),\n        ),\n        (b\"\\x35\\x66\\x6f\\x68\".to_vec(), \"D-Link DAP-1665\".to_string()),\n        (\n            b\"\\xf5\\x2a\\xa0\\xb4\".to_vec(),\n            \"D-Link DIR-842 rev C\".to_string(),\n        ),\n        (\n            b\"\\xe3\\x13\\x00\\x5b\".to_vec(),\n            \"D-Link DIR-850 rev A\".to_string(),\n        ),\n        (\n            b\"\\x0a\\x14\\xe4\\x24\".to_vec(),\n            \"D-Link DIR-850 rev B\".to_string(),\n        ),\n    ])\n}\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Known encrypted firmware\";\n\n/// Known encrypted firmware magic bytes\npub fn encfw_magic() -> Vec<Vec<u8>> {\n    encfw_known_firmware().keys().cloned().collect()\n}\n\n/// Parse the magic signature match\npub fn encfw_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const MAGIC_LEN: usize = 4;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Some(magic_bytes) = file_data.get(offset..offset + MAGIC_LEN) {\n        if encfw_known_firmware().contains_key(magic_bytes) {\n            if result.offset != 0 {\n                result.confidence = CONFIDENCE_LOW;\n            }\n\n            result.description = format!(\n                \"{}, {}\",\n                result.description,\n                encfw_known_firmware()[magic_bytes]\n            );\n\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/encrpted_img.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"D-Link Encrpted Image\";\n\n/// encrpted_img firmware images always start with these bytes\npub fn encrpted_img_magic() -> Vec<Vec<u8>> {\n    vec![b\"encrpted_img\".to_vec()]\n}\n\n/// Validates the encrpted_img header\npub fn encrpted_img_parser(\n    _file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Nothing to really validate here\n    if offset != 0 {\n        result.confidence = CONFIDENCE_LOW;\n    }\n\n    Ok(result)\n}\n"
  },
  {
    "path": "src/signatures/ext.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::ext::parse_ext_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"EXT filesystem\";\n\n/// EXT magic bytes\npub fn ext_magic() -> Vec<Vec<u8>> {\n    /*\n     * The magic bytes for EXT are only a u16, resulting in many false positives.\n     * These magic signatures include all possible values for the state and errors fields in the superblock,\n     * as well as the minor version number (assumed to be 0).\n     * This means fewer false positive matches, and less time spent validating false positives.\n     */\n    vec![\n        b\"\\x53\\xEF\\x01\\x00\\x01\\x00\\x00\\x00\".to_vec(),\n        b\"\\x53\\xEF\\x01\\x00\\x02\\x00\\x00\\x00\".to_vec(),\n        b\"\\x53\\xEF\\x01\\x00\\x03\\x00\\x00\\x00\".to_vec(),\n        b\"\\x53\\xEF\\x02\\x00\\x01\\x00\\x00\\x00\".to_vec(),\n        b\"\\x53\\xEF\\x02\\x00\\x02\\x00\\x00\\x00\".to_vec(),\n        b\"\\x53\\xEF\\x02\\x00\\x03\\x00\\x00\\x00\".to_vec(),\n    ]\n}\n\n/// Parse the EXT signature\npub fn ext_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Offset inside the EXT image where the magic bytes reside\n    const MAGIC_OFFSET: usize = 1080;\n\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset: offset - MAGIC_OFFSET,\n        size: 0,\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Some(ext_data) = file_data.get(result.offset..) {\n        if let Ok(ext_header) = parse_ext_header(ext_data) {\n            result.size = ext_header.image_size;\n            result.description = format!(\n                \"{} for {}, inodes: {}, block size: {}, block count: {}, free blocks: {}, reserved blocks: {}, total size: {} bytes\",\n                result.description,\n                ext_header.os,\n                ext_header.inodes_count,\n                ext_header.block_size,\n                ext_header.free_blocks_count,\n                ext_header.reserved_blocks_count,\n                ext_header.blocks_count,\n                result.size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/fat.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::fat::parse_fat_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"FAT file system\";\n\n/// Offset of magic bytes from the start of the FAT\npub const MAGIC_OFFSET: usize = 0x01FE;\n\n/// FAT always contains these bytes\npub fn fat_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x55\\xAA\".to_vec()]\n}\n\n/// Validates the FAT header\npub fn fat_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Sanity check the magic offset\n    if offset >= MAGIC_OFFSET {\n        // FAT actually starts this may bytes before the magic bytes\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Parse and validate the FAT header\n        if let Some(fat_data) = file_data.get(result.offset..) {\n            if let Ok(fat_header) = parse_fat_header(fat_data) {\n                // Report the total size of the FAT image\n                result.size = fat_header.total_size;\n\n                // Include FAT type in the description\n                let mut fat_type_desc: &str = \"FAT12/16\";\n                if fat_header.is_fat32 {\n                    fat_type_desc = \"FAT32\";\n                }\n\n                result.description = format!(\n                    \"{}, type: {}, total size: {} bytes\",\n                    result.description, fat_type_desc, result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/gif.rs",
    "content": "use crate::extractors::gif::extract_gif_image;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::gif::parse_gif_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"GIF image\";\n\n/// GIF images always start with these bytes\npub fn gif_magic() -> Vec<Vec<u8>> {\n    // https://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html\n    vec![b\"GIF87a\".to_vec(), b\"GIF89a\".to_vec()]\n}\n\n/// Validates the GIF header\npub fn gif_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do an extraction dry-run to validate the GIF image\n    let dry_run = extract_gif_image(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(total_size) = dry_run.size {\n            // Everything looks ok, parse the GIF header to report some info to the user\n            if let Ok(gif_header) = parse_gif_header(&file_data[offset..]) {\n                // No sense in extracting a GIF from a file if the GIF data starts at offset 0\n                if offset == 0 {\n                    result.extraction_declined = true;\n                }\n\n                result.size = total_size;\n                result.description = format!(\n                    \"{}, {}x{} pixels, total size: {} bytes\",\n                    result.description,\n                    gif_header.image_width,\n                    gif_header.image_height,\n                    result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/gpg.rs",
    "content": "use crate::extractors::gpg::gpg_decompress;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const GPG_SIGNED_DESCRIPTION: &str = \"GPG signed file\";\n\n/// GPG signed files start with these two bytes\npub fn gpg_signed_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xA3\\x01\".to_vec()]\n}\n\n/// Validates GPG signatures\npub fn gpg_signed_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Success result; confidence is high since this signature is only reported what it starts at the beginning of a file\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        description: GPG_SIGNED_DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    /*\n     * GPG signed files are just zlib compressed files with the zlib magic bytes replaced with the GPG magic bytes.\n     * Decompress the signed file; no output directory specified, dry run only.\n     */\n    let decompression_dry_run = gpg_decompress(file_data, offset, None);\n\n    // If the decompression dry run was a success, this signature is almost certianly valid\n    if decompression_dry_run.success {\n        if let Some(total_size) = decompression_dry_run.size {\n            result.size = total_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/gzip.rs",
    "content": "use crate::common;\nuse crate::extractors::gzip::gzip_decompress;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::gzip::parse_gzip_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"gzip compressed data\";\n\n/// Gzip magic bytes, plus compression type field (always 8 for deflate)\npub fn gzip_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x1f\\x8b\\x08\".to_vec()]\n}\n\n/// Validates gzip signatures\npub fn gzip_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Length of the GZIP CRC located at the end of the deflate data stream\n    const GZIP_CRC_SIZE: usize = 4;\n    // Length of the ISIZE field located after the CRC field\n    const GZIP_ISIZE_SIZE: usize = 4;\n\n    // Do a dry-run decompression\n    let dry_run = gzip_decompress(file_data, offset, None);\n\n    // If dry-run was successful, this is almost certianly a valid gzip file\n    if dry_run.success {\n        // Get the size of the deflate data stream\n        if let Some(deflate_data_size) = dry_run.size {\n            // The dry run has already validated the header, but we want some header info to display to the user\n            if let Ok(gzip_header) = parse_gzip_header(&file_data[offset..]) {\n                // Original file name is optional\n                let mut original_file_name_text: String = \"\".to_string();\n\n                if !gzip_header.original_name.is_empty() {\n                    original_file_name_text =\n                        format!(\" original file name: \\\"{}\\\",\", gzip_header.original_name);\n                }\n\n                // Total size of the gzip file is the size of the header, plus the size of the compressed data, plus the trailing CRC and ISIZE fields\n                let total_size =\n                    gzip_header.size + deflate_data_size + GZIP_CRC_SIZE + GZIP_ISIZE_SIZE;\n\n                return Ok(SignatureResult {\n                    offset,\n                    size: total_size,\n                    confidence: CONFIDENCE_HIGH,\n                    description: format!(\n                        \"{},{} operating system: {}, timestamp: {}, total size: {} bytes\",\n                        DESCRIPTION,\n                        original_file_name_text,\n                        gzip_header.os,\n                        common::epoch_to_string(gzip_header.timestamp),\n                        total_size,\n                    ),\n                    ..Default::default()\n                });\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/hashes.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\n\n/// All hash magics here are the same size\nconst HASH_MAGIC_LEN: usize = 16;\n\n/// Human readable descriptions\npub const CRC32_DESCRIPTION: &str = \"CRC32 polynomial table\";\npub const SHA256_DESCRIPTION: &str = \"SHA256 hash constants\";\npub const MD5_DESCRIPTION: &str = \"MD5 hash constants\";\n\n/// CRC32 contstants\npub fn crc32_magic() -> Vec<Vec<u8>> {\n    // Order matters! See hash_endianness().\n    vec![\n        // Big endian\n        b\"\\x00\\x00\\x00\\x00\\x77\\x07\\x30\\x96\\xEE\\x0E\\x61\\x2C\\x99\\x09\\x51\\xBA\".to_vec(),\n        // Little endian\n        b\"\\x00\\x00\\x00\\x00\\x96\\x30\\x07\\x77\\x2C\\x61\\x0E\\xEE\\xBA\\x51\\x09\\x99\".to_vec(),\n    ]\n}\n\n/// SHA256 constants\npub fn sha256_magic() -> Vec<Vec<u8>> {\n    // Order matters! See hash_endianness().\n    vec![\n        // Big endian\n        b\"\\x42\\x8a\\x2f\\x98\\x71\\x37\\x44\\x91\\xb5\\xc0\\xfb\\xcf\\xe9\\xb5\\xdb\\xa5\".to_vec(),\n        // Little endian\n        b\"\\x98\\x2f\\x8a\\x42\\x91\\x44\\x37\\x71\\xcf\\xfb\\xc0\\xb5\\xa5\\xdb\\xb5\\xe9\".to_vec(),\n    ]\n}\n\n/// MD5 constants\npub fn md5_magic() -> Vec<Vec<u8>> {\n    vec![\n        // Big endian\n        b\"\\xd7\\x6a\\xa4\\x78\\xe8\\xc7\\xb7\\x56\\x24\\x20\\x70\\xdb\\xc1\\xbd\\xce\\xee\".to_vec(),\n        // Little endian\n        b\"\\x78\\xa4\\x6a\\xd7\\x56\\xb7\\xc7\\xe8\\xdb\\x70\\x20\\x24\\xee\\xce\\xbd\\xc1\".to_vec(),\n    ]\n}\n\npub fn crc32_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Just return a success with some extra description info\n    let result = SignatureResult {\n        description: format!(\n            \"{}, {} endian\",\n            CRC32_DESCRIPTION,\n            hash_endianess(file_data, offset, crc32_magic())\n        ),\n        offset,\n        size: HASH_MAGIC_LEN,\n        ..Default::default()\n    };\n\n    Ok(result)\n}\n\npub fn sha256_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Just return a success with some extra description info\n    let result = SignatureResult {\n        description: format!(\n            \"{}, {} endian\",\n            SHA256_DESCRIPTION,\n            hash_endianess(file_data, offset, sha256_magic())\n        ),\n        offset,\n        size: HASH_MAGIC_LEN,\n        ..Default::default()\n    };\n\n    Ok(result)\n}\n\npub fn md5_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Just return a success with some extra description info\n    let result = SignatureResult {\n        description: format!(\n            \"{}, {} endian\",\n            MD5_DESCRIPTION,\n            hash_endianess(file_data, offset, md5_magic())\n        ),\n        offset,\n        size: HASH_MAGIC_LEN,\n        ..Default::default()\n    };\n\n    Ok(result)\n}\n\n/// Detects hash contstant endianess\nfn hash_endianess(file_data: &[u8], offset: usize, magics: Vec<Vec<u8>>) -> String {\n    let mut endianness: String = \"little\".to_string();\n    let this_magic = &file_data[offset..offset + HASH_MAGIC_LEN];\n\n    if *this_magic == magics[0] {\n        endianness = \"big\".to_string();\n    }\n\n    endianness\n}\n"
  },
  {
    "path": "src/signatures/iso9660.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::iso9660::parse_iso_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"ISO9660 primary volume\";\n\n/// ISOs start with these magic bytes\npub fn iso_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x01CD001\\x01\\x00\".to_vec()]\n}\n\n/// Validate ISO signatures\npub fn iso_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Offset from the beginning of the ISO image to the magic bytes\n    const ISO_MAGIC_OFFSET: usize = 32768;\n\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // We need at least ISO_MAGIC_OFFSET bytes to exist before the magic match offset\n    if offset >= ISO_MAGIC_OFFSET {\n        // Calculate the actual starting offset of the ISO\n        result.offset = offset - ISO_MAGIC_OFFSET;\n\n        // Parse the header, if parsing succeeds assume it's valid\n        if let Ok(iso_header) = parse_iso_header(&file_data[result.offset..]) {\n            result.size = iso_header.image_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/jboot.rs",
    "content": "use crate::extractors::jboot::extract_jboot_sch2_kernel;\nuse crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::jboot::{\n    parse_jboot_arm_header, parse_jboot_sch2_header, parse_jboot_stag_header,\n};\n\n/// Human readable descriptions\npub const JBOOT_ARM_DESCRIPTION: &str = \"JBOOT firmware header\";\npub const JBOOT_STAG_DESCRIPTION: &str = \"JBOOT STAG header\";\npub const JBOOT_SCH2_DESCRIPTION: &str = \"JBOOT SCH2 header\";\n\n/// JBOOT firmware header magic bytes\npub fn jboot_arm_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x42\\x48\".to_vec()]\n}\n\n/// JBOOT STAG header magic bytes\npub fn jboot_stag_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x04\\x04\\x24\\x2B\".to_vec(), b\"\\xFF\\x04\\x24\\x2B\".to_vec()]\n}\n\n/// JBOOT SCH2 header magic bytes\npub fn jboot_sch2_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x24\\x21\\x00\\x02\".to_vec(),\n        b\"\\x24\\x21\\x01\\x02\".to_vec(),\n        b\"\\x24\\x21\\x02\\x02\".to_vec(),\n        b\"\\x24\\x21\\x03\\x02\".to_vec(),\n    ]\n}\n\n/// Parse and validate the JBOOT ARM header\npub fn jboot_arm_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Magic bytes start at this offset into the header\n    const MAGIC_OFFSET: usize = 48;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        description: JBOOT_ARM_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Actual header starts MAGIC_OFFSET bytes before the magic bytes\n    let header_start = offset - MAGIC_OFFSET;\n\n    if let Some(jboot_data) = file_data.get(header_start..) {\n        if let Ok(arm_header) = parse_jboot_arm_header(jboot_data) {\n            result.size = arm_header.header_size;\n            result.offset = header_start;\n            result.description = format!(\n                \"{}, header size: {} bytes, ROM ID: {}, erase offset: {:#X}, erase size: {:#X}, data flash offset: {:#X}, data size: {:#X}\",\n                result.description,\n                arm_header.header_size,\n                arm_header.rom_id,\n                arm_header.erase_offset,\n                arm_header.erase_size,\n                arm_header.data_offset,\n                arm_header.data_size,\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Parse and validate a JBOOT STAG header\npub fn jboot_stag_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: JBOOT_STAG_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    if let Ok(stag_header) = parse_jboot_stag_header(&file_data[offset..]) {\n        // Sanity check on the stag header reported size; it is expected that this\n        // type of header describes a kernel, and should not take up the entire firmware image\n        if (offset + stag_header.header_size + stag_header.image_size) < file_data.len() {\n            // Only report the header size, confidence in this signature is low, don't\n            // want to skip a bunch of data on a false positive\n            result.size = stag_header.header_size;\n\n            let mut image_type: &str = \"factory image\";\n\n            if stag_header.is_sysupgrade_image {\n                image_type = \"system upgrade image\";\n            }\n\n            result.description = format!(\n                \"{}, {}, header size: {} bytes, kernel data size: {} bytes\",\n                result.description, image_type, stag_header.header_size, stag_header.image_size,\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Parse and validate a JBOOT SCH2 header\npub fn jboot_sch2_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: JBOOT_SCH2_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let dry_run = extract_jboot_sch2_kernel(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(total_size) = dry_run.size {\n            if let Ok(sch2_header) = parse_jboot_sch2_header(&file_data[offset..]) {\n                result.size = total_size;\n                result.description = format!(\n                    \"{}, header size: {} bytes, kernel size: {} bytes, kernel compression: {}, kernel entry point: {:#X}\",\n                    result.description,\n                    sch2_header.header_size,\n                    sch2_header.kernel_size,\n                    sch2_header.compression,\n                    sch2_header.kernel_entry_point,\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/jffs2.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::jffs2::{JFFS2_NODE_STRUCT_SIZE, parse_jffs2_node_header};\nuse aho_corasick::AhoCorasick;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"JFFS2 filesystem\";\n\n/// JFFS2 magic bytes\npub fn jffs2_magic() -> Vec<Vec<u8>> {\n    /*\n     * Big and little endian patterns to search for.\n     * These assume that the first JFFS2 node will be a directory, inode, or clean marker type.\n     * Longer signatures are less prone to false positive matches.\n     */\n    vec![\n        b\"\\x19\\x85\\xe0\\x01\".to_vec(),\n        b\"\\x19\\x85\\xe0\\x02\".to_vec(),\n        b\"\\x19\\x85\\x20\\x03\".to_vec(),\n        b\"\\x85\\x19\\x01\\xe0\".to_vec(),\n        b\"\\x85\\x19\\x02\\xe0\".to_vec(),\n        b\"\\x85\\x19\\x03\\x20\".to_vec(),\n    ]\n}\n\n/// Parse and validate a JFFS2 image\npub fn jffs2_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Useful contstants\n    const MAX_PAGE_SIZE: usize = 0x20000;\n    const MIN_VALID_NODE_COUNT: usize = 2;\n    const JFFS2_BIG_ENDIAN_MAGIC: &[u8; 2] = b\"\\x19\\x85\";\n    const JFFS2_LITTLE_ENDIAN_MAGIC: &[u8; 2] = b\"\\x85\\x19\";\n\n    let mut result = SignatureResult {\n        size: 0,\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse this first JFFS2 node header to ensure correctness\n    if let Ok(first_node_header) = parse_jffs2_node_header(&file_data[offset..]) {\n        // The known end of JFFS2 data in the raw file data. This will be updated as we find more nodes.\n        let mut jffs2_eof: usize = offset + roundup(first_node_header.size);\n\n        // Make sure that jffs2_eof is sane\n        if jffs2_eof < file_data.len() {\n            // Start searching for subsequent JFFS2 nodes at the end of this node's data\n            let grep_offset: usize = jffs2_eof;\n\n            // Keep a count of how many valid nodes were found\n            let mut node_count: usize = 1;\n\n            // Determine which node magic bytes to search for based on the first node's endianness\n            let mut node_magic = JFFS2_LITTLE_ENDIAN_MAGIC;\n            if first_node_header.endianness == \"big\" {\n                node_magic = JFFS2_BIG_ENDIAN_MAGIC;\n            }\n\n            // Need to grep for all JFFS2 nodes to figure out how big this file system really is\n            let grep = AhoCorasick::new(vec![node_magic]).unwrap();\n\n            // Find all matching JFFS2 node magic bytes\n            for magic_match in grep.find_overlapping_iter(&file_data[grep_offset..].to_vec()) {\n                // Calculate the start and end of the node header inside the file data\n                let header_start: usize = grep_offset + magic_match.start();\n                let header_end: usize = header_start + JFFS2_NODE_STRUCT_SIZE;\n\n                // This is a false positive that is inside the node data of a previously validated node\n                if header_start < jffs2_eof {\n                    continue;\n                }\n\n                // If we haven't found a valid header within MAX_PAGE_SIZE bytes, quit\n                if (header_start - jffs2_eof) > MAX_PAGE_SIZE {\n                    break;\n                }\n\n                // Get the node header's raw bytes\n                match file_data.get(header_start..header_end) {\n                    None => {\n                        break;\n                    }\n                    Some(node_header_data) => {\n                        // Parse this node's header\n                        if let Ok(this_node_header) = parse_jffs2_node_header(node_header_data) {\n                            node_count += 1;\n                            jffs2_eof = header_start + roundup(this_node_header.size);\n                        }\n                    }\n                }\n            }\n\n            // Make sure we've processed at least a few JFFS2 nodes\n            if node_count > MIN_VALID_NODE_COUNT {\n                result.size = jffs2_eof - result.offset;\n                result.description = format!(\n                    \"{}, {} endian, nodes: {}, total size: {} bytes\",\n                    result.description, first_node_header.endianness, node_count, result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// JFFS2 nodes are padded to a 4 byte boundary\nfn roundup(num: usize) -> usize {\n    let base: f64 = 4.0;\n    let number: f64 = num as f64;\n    let div: f64 = number / base;\n    (base * div.ceil()) as usize\n}\n"
  },
  {
    "path": "src/signatures/jpeg.rs",
    "content": "use crate::extractors::jpeg::extract_jpeg_image;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"JPEG image\";\n\n/// JPEG magic bytes\npub fn jpeg_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\".to_vec(),\n        b\"\\xFF\\xD8\\xFF\\xE1\".to_vec(),\n        b\"\\xFF\\xD8\\xFF\\xDB\".to_vec(),\n    ]\n}\n\n/// Parse a JPEG image\npub fn jpeg_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Perform an extraction dry-run\n    let dry_run = extract_jpeg_image(file_data, offset, None);\n\n    // If the dry-run was a success, this is probably a valid JPEG file\n    if dry_run.success {\n        // Get the total size of the JPEG\n        if let Some(jpeg_size) = dry_run.size {\n            // Report signature result data\n            result.size = jpeg_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n\n            // If this entire file is a JPEG, no need to extract it\n            if offset == 0 && result.size == file_data.len() {\n                result.extraction_declined = true;\n            }\n\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/linux.rs",
    "content": "use crate::common::get_cstring;\nuse crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::linux::{\n    parse_linux_arm_zimage_header, parse_linux_arm64_boot_image_header,\n};\nuse aho_corasick::AhoCorasick;\n\n/// Human readable descriptions\npub const LINUX_ARM_ZIMAGE_DESCRIPTION: &str = \"Linux ARM boot executable zImage\";\npub const LINUX_BOOT_IMAGE_DESCRIPTION: &str = \"Linux kernel boot image\";\npub const LINUX_KERNEL_VERSION_DESCRIPTION: &str = \"Linux kernel version\";\npub const LINUX_ARM64_BOOT_IMAGE_DESCRIPTION: &str = \"Linux kernel ARM64 boot image\";\n\n/// Magic bytes for a linux boot image\npub fn linux_boot_image_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xb8\\xc0\\x07\\x8e\\xd8\\xb8\\x00\\x90\\x8e\\xc0\\xb9\\x00\\x01\\x29\\xf6\\x29\".to_vec()]\n}\n\n/// Kernel version string magic\npub fn linux_kernel_version_magic() -> Vec<Vec<u8>> {\n    vec![b\"Linux\\x20version\\x20\".to_vec()]\n}\n\n/// Magic bytes for a linux ARM64 boot image\npub fn linux_arm64_boot_image_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00ARMd\".to_vec()]\n}\n\n/// Magic bytes for Linux ARM zImage\npub fn linux_arm_zimage_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x18\\x28\\x6F\\x01\".to_vec(), b\"\\x01\\x6F\\x28\\x18\".to_vec()]\n}\n\n/// Validate a Linux ARM zImage\npub fn linux_arm_zimage_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    const MAGIC_OFFSET: usize = 36;\n\n    let mut result = SignatureResult {\n        confidence: CONFIDENCE_MEDIUM,\n        description: LINUX_ARM_ZIMAGE_DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    if offset >= MAGIC_OFFSET {\n        result.offset = offset - MAGIC_OFFSET;\n\n        if let Some(zimage_data) = file_data.get(result.offset..) {\n            if let Ok(zimage_header) = parse_linux_arm_zimage_header(zimage_data) {\n                result.description = format!(\n                    \"{}, {} endian\",\n                    result.description, zimage_header.endianness\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Validate a linux ARM64 boot image signature\npub fn linux_arm64_boot_image_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Magic bytes are 56 bytes into the image\n    const MAGIC_OFFSET: usize = 0x30;\n\n    let mut result = SignatureResult {\n        confidence: CONFIDENCE_MEDIUM,\n        description: LINUX_ARM64_BOOT_IMAGE_DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    if offset >= MAGIC_OFFSET {\n        // Set the real starting offset\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Parse and validate the header data\n        if let Ok(image_header) = parse_linux_arm64_boot_image_header(&file_data[result.offset..]) {\n            result.size = image_header.header_size;\n            result.description = format!(\n                \"{}, {} endian, effective image size: {} bytes\",\n                result.description, image_header.endianness, image_header.image_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Validate a linux boot image signature\npub fn linux_boot_image_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // There should be the string \"!HdrS\" 514 bytes from the start of the magic signature\n    const HDRS_OFFSET: usize = 514;\n    const HDRS_EXPECTED_VALUE: &str = \"!HdrS\";\n\n    let result = SignatureResult {\n        description: LINUX_BOOT_IMAGE_DESCRIPTION.to_string(),\n        offset,\n        size: 0,\n        ..Default::default()\n    };\n\n    // Calculate start and end offset of the expected !HdrS string\n    let hdrs_start: usize = offset + HDRS_OFFSET;\n    let hdrs_end: usize = hdrs_start + HDRS_EXPECTED_VALUE.len();\n\n    if let Some(hdrs_bytes) = file_data.get(hdrs_start..hdrs_end) {\n        // Get the string that should equal HDRS_EXPECTED_VALUE\n        if let Ok(actual_hdrs_value) = String::from_utf8(hdrs_bytes.to_vec()) {\n            // Validate that the hdrs string matches\n            if actual_hdrs_value == HDRS_EXPECTED_VALUE {\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Validate a linux kernel version signature and detect if a symbol table is present\npub fn linux_kernel_version_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Kernel version string format is expected to be something like:\n    // \"Linux version 4.9.241 (root@server2) (gcc version 10.0.1 (OpenWrt GCC 10.0.1 r12423-0493d57e04) ) #755 SMP Wed Nov 4 03:59:02 +03 2020\\n\"\n    const PERIOD: u8 = 0x2E;\n    const NEW_LINE: &str = \"\\n\";\n    const AMPERSAND: &str = \"@\";\n    const PERIOD_OFFSET_1: usize = 15;\n    const PERIOD_OFFSET_2: usize = 17;\n    const PERIOD_OFFSET_3: usize = 18;\n    const MIN_FILE_SIZE: usize = 100 * 1024;\n    const MIN_VERSION_STRING_LENGTH: usize = 75;\n    const GCC_VERSION_STRING: &str = \"gcc \";\n\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    let file_size = file_data.len();\n\n    // Sanity check the size of the file; this automatically eliminates small text files that might match the magic bytes\n    if file_size > MIN_FILE_SIZE {\n        // Get the kernel version string\n        let kernel_version_string = get_cstring(&file_data[offset..]);\n\n        // Sanity check the length of the version string\n        if kernel_version_string.len() > MIN_VERSION_STRING_LENGTH {\n            // Make sure the string includes the GCC version string too\n            if kernel_version_string.contains(GCC_VERSION_STRING) {\n                // Make sure the string includes an ampersand\n                if kernel_version_string.contains(AMPERSAND) {\n                    // The kernel version string should end with a new line\n                    if kernel_version_string.ends_with(NEW_LINE) {\n                        let kv_bytes = kernel_version_string.as_bytes();\n\n                        // Make sure the linux kernel version has periods at the expected locations\n                        if kv_bytes[PERIOD_OFFSET_1] == PERIOD\n                            && (kv_bytes[PERIOD_OFFSET_2] == PERIOD\n                                || kv_bytes[PERIOD_OFFSET_3] == PERIOD)\n                        {\n                            // Try to locate a Linux kernel symbol table\n                            let symtab_present = has_linux_symbol_table(file_data);\n\n                            // If a symbol table is present, assume the entire file is a raw Linux kernel.\n                            // This is necessary for vmlinux-to-elf extraction.\n                            // Otherwise just report the kernel version string and decline extraction.\n                            if symtab_present {\n                                result.offset = 0;\n                                result.size = file_data.len();\n                            } else {\n                                result.size = kernel_version_string.len();\n                                result.extraction_declined = true;\n                            }\n\n                            // Report the result\n                            result.description = format!(\n                                \"{}, has symbol table: {}\",\n                                kernel_version_string.trim(),\n                                symtab_present\n                            );\n                            return Ok(result);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Searches the file data for a linux symbol table\nfn has_linux_symbol_table(file_data: &[u8]) -> bool {\n    let mut match_count: usize = 0;\n\n    // Same magic bytes that vmlinux-to-elf searches for\n    let symtab_magic = vec![b\"\\x000\\x001\\x002\\x003\\x004\\x005\\x006\\x007\\x008\\x009\\x00\"];\n\n    let grep = AhoCorasick::new(symtab_magic).unwrap();\n\n    // Grep for matches on the Linux symbol table magic bytes\n    for _ in grep.find_overlapping_iter(file_data) {\n        match_count += 1;\n    }\n\n    // There should be only one match\n    match_count == 1\n}\n"
  },
  {
    "path": "src/signatures/logfs.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::logfs::{LOGFS_MAGIC_OFFSET, parse_logfs_super_block};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LogFS file system\";\n\n/// LogFS magic bytes\npub fn logfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x7A\\x3A\\x8E\\x5C\\xB9\\xD5\\xBF\\x67\".to_vec()]\n}\n\n/// Validates the LogFS super block\npub fn logfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if offset >= LOGFS_MAGIC_OFFSET {\n        result.offset = offset - LOGFS_MAGIC_OFFSET;\n\n        if let Some(logfs_sb_data) = file_data.get(result.offset..) {\n            if let Ok(logfs_super_block) = parse_logfs_super_block(logfs_sb_data) {\n                result.size = logfs_super_block.total_size;\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/luks.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::luks::parse_luks_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LUKS header\";\n\n/// LUKS Headers start with these bytes\npub fn luks_magic() -> Vec<Vec<u8>> {\n    vec![b\"LUKS\\xBA\\xBE\".to_vec()]\n}\n\n/// Parse and validate the LUKS header\npub fn luks_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful result\n    let mut result = SignatureResult {\n        offset,\n        name: \"luks\".to_string(),\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // If the header is parsed successfully, consider it valid\n    if let Ok(luks_header) = parse_luks_header(&file_data[offset..]) {\n        // Version 1 and version 2 have different header fields\n        if luks_header.version == 1 {\n            result.description = format!(\n                \"{}, version: {}, cipher algorithm: {}, cipher mode: {}, hash fn: {}\",\n                result.description,\n                luks_header.version,\n                luks_header.cipher_algorithm,\n                luks_header.cipher_mode,\n                luks_header.hashfn\n            );\n        } else {\n            result.description = format!(\n                \"{}, version: {}, header size: {} bytes, hash fn: {}\",\n                result.description,\n                luks_header.version,\n                luks_header.header_size,\n                luks_header.hashfn\n            );\n        }\n\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/lz4.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::lz4::{parse_lz4_block_header, parse_lz4_file_header};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LZ4 compressed data\";\n\n/// LZ4 files start with these magic bytes\npub fn lz4_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x04\\x22\\x4D\\x18\".to_vec()]\n}\n\n/// Validate a LZ4 signature\npub fn lz4_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Checksums are 4 bytes in length\n    const CONTENT_CHECKSUM_LEN: usize = 4;\n\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_MEDIUM,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    // Sanity check the size of available data\n    if let Ok(lz4_file_header) = parse_lz4_file_header(&file_data[offset..]) {\n        // LZ4 data starts immediately after the LZ4 header\n        if let Some(lz4_data) = file_data.get(offset + lz4_file_header.header_size..) {\n            // Determine the size of the actual LZ4 data by processing the data blocks that immediately follow the file header\n            if let Ok(lz4_data_size) =\n                get_lz4_data_size(lz4_data, lz4_file_header.block_checksum_present)\n            {\n                // Set the size of the header and the LZ4 data\n                result.size = lz4_file_header.header_size + lz4_data_size;\n\n                // If this flag is set, an additional 4-byte checksum will be present at the end of the LZ4 data\n                if lz4_file_header.content_checksum_present {\n                    result.size += CONTENT_CHECKSUM_LEN;\n                }\n\n                // Update description\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Processes the LZ4 data blocks and returns the size of the raw LZ4 data\nfn get_lz4_data_size(lz4_data: &[u8], checksum_present: bool) -> Result<usize, SignatureError> {\n    let mut lz4_data_size: usize = 0;\n    let mut last_lz4_data_size = None;\n    let available_data = lz4_data.len();\n\n    // Loop while there is still data and while the offsets are sane\n    while is_offset_safe(available_data, lz4_data_size, last_lz4_data_size) {\n        // Get the next block's data\n        match lz4_data.get(lz4_data_size..) {\n            None => {\n                break;\n            }\n            Some(lz4_block_data) => {\n                // Parse the next block's data\n                match parse_lz4_block_header(lz4_block_data, checksum_present) {\n                    Err(_) => {\n                        break;\n                    }\n                    Ok(block_header) => {\n                        // Update offsets\n                        last_lz4_data_size = Some(lz4_data_size);\n                        lz4_data_size += block_header.header_size\n                            + block_header.data_size\n                            + block_header.checksum_size;\n\n                        // Only return success if a last block header is found\n                        if block_header.last_block {\n                            return Ok(lz4_data_size);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/lzfse.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::lzfse::parse_lzfse_block_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LZFSE compressed data\";\n\n/// LZFSE block magics\npub fn lzfse_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"bvx-\".to_vec(),\n        b\"bvx1\".to_vec(),\n        b\"bvx2\".to_vec(),\n        b\"bvxn\".to_vec(),\n    ]\n}\n\n/// Validate LZFSE signatures\npub fn lzfse_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    let available_data = file_data.len();\n    let mut next_block_offset = offset;\n    let mut previous_block_offset = None;\n\n    // Walk through all the LZFSE blocks until an end of stream block is found\n    while is_offset_safe(available_data, next_block_offset, previous_block_offset) {\n        // Update previous block offset value in preparation for the next loop\n        previous_block_offset = Some(next_block_offset);\n\n        // Parse the next block\n        if let Ok(lzfse_block) = parse_lzfse_block_header(&file_data[next_block_offset..]) {\n            next_block_offset += lzfse_block.header_size + lzfse_block.data_size;\n\n            // Only return success if an end-of-stream block is found\n            if lzfse_block.eof {\n                result.size = next_block_offset - offset;\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/lzma.rs",
    "content": "use crate::extractors::lzma;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::lzma::parse_lzma_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LZMA compressed data\";\n\n/// Builds a list of common LZMA magic bytes (properties + dictionary sizes)\npub fn lzma_magic() -> Vec<Vec<u8>> {\n    let mut magic_signatures: Vec<Vec<u8>> = vec![];\n\n    // Common LZMA properties\n    let supported_properties: Vec<u8> = vec![0x5D, 0x6E, 0x6D, 0x6C];\n\n    let supported_dictionary_sizes: Vec<u32> = vec![\n        0x10_00_00_00,\n        0x20_00_00_00,\n        0x01_00_00_00,\n        0x02_00_00_00,\n        0x04_00_00_00,\n        0x00_80_00_00,\n        0x00_40_00_00,\n        0x00_20_00_00,\n        0x00_10_00_00,\n        0x00_08_00_00,\n        0x00_02_00_00,\n        0x00_01_00_00,\n    ];\n\n    /*\n     * Build a list of magic signatures to search for based on the supported property and dictionary values.\n     * This means having a lot of LZMA signatures, but they are less prone to false positives than searching\n     * for a more generic, but shorter, signature, such as b\"\\x5d\\x00\\x00\". This results in less validation\n     * of false positives, improving analysis times.\n     */\n    for property in supported_properties {\n        for dictionary_size in &supported_dictionary_sizes {\n            let mut magic: Vec<u8> = vec![property];\n            magic.extend(dictionary_size.to_le_bytes().to_vec());\n            magic_signatures.push(magic.to_vec());\n        }\n    }\n\n    magic_signatures\n}\n\n/// Validate LZMA signatures\npub fn lzma_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the LZMA header\n    if let Ok(lzma_header) = parse_lzma_header(&file_data[offset..]) {\n        /*\n         * LZMA signatures are very prone to false positives, so do a dry-run extraction.\n         * If it succeeds, we have high confidence that this signature is valid.\n         * Else, assume this is a false positive.\n         */\n        let dry_run = lzma::lzma_decompress(file_data, offset, None);\n\n        // Return success if dry run succeeded\n        if dry_run.success {\n            if let Some(lzma_stream_size) = dry_run.size {\n                result.size = lzma_stream_size;\n                result.description = format!(\n                    \"{}, properties: {:#04X}, dictionary size: {} bytes, compressed size: {} bytes, uncompressed size: {} bytes\",\n                    result.description,\n                    lzma_header.properties,\n                    lzma_header.dictionary_size,\n                    result.size,\n                    lzma_header.decompressed_size as i64\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/lzop.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::lzop::{\n    parse_lzop_block_header, parse_lzop_eof_marker, parse_lzop_file_header,\n};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"LZO compressed data\";\n\n/// LZOP magic bytes\npub fn lzop_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x89LZO\\x00\\x0D\\x0A\\x1A\\x0A\".to_vec()]\n}\n\n/// Validate an LZOP signature\npub fn lzop_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success retrun value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the LZOP file header\n    if let Ok(lzop_header) = parse_lzop_file_header(&file_data[offset..]) {\n        if let Some(lzop_data) = file_data.get(offset + lzop_header.header_size..) {\n            // Get the size of the compressed LZO data\n            if let Ok(data_size) = get_lzo_data_size(lzop_data, lzop_header.block_checksum_present)\n            {\n                // Update the total size to include the LZO data\n                result.size = lzop_header.header_size + data_size;\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n// Parse the LZO blocks to determine the size of the compressed data, including the terminating EOF marker\nfn get_lzo_data_size(\n    lzo_data: &[u8],\n    compressed_checksum_present: bool,\n) -> Result<usize, SignatureError> {\n    // Technially LZOP could have one block, but this would seem uncommon\n    const MIN_BLOCK_COUNT: usize = 2;\n\n    let available_data = lzo_data.len();\n    let mut last_offset = None;\n    let mut data_size: usize = 0;\n    let mut block_count: usize = 0;\n\n    // Loop until we run out of data or an invalid block header is encountered\n    while is_offset_safe(available_data, data_size, last_offset) {\n        // Parse the next block header\n        match parse_lzop_block_header(&lzo_data[data_size..], compressed_checksum_present) {\n            Err(_) => {\n                break;\n            }\n\n            Ok(block_header) => {\n                // Update block count, offset, and size\n                block_count += 1;\n                last_offset = Some(data_size);\n                data_size += block_header.header_size\n                    + block_header.compressed_size\n                    + block_header.checksum_size;\n            }\n        }\n    }\n\n    // As a sanity check, make sure we processed some number of data blocks\n    if block_count >= MIN_BLOCK_COUNT {\n        // Process the EOF marker that should come at the end of the data blocks\n        if let Some(eof_marker_data) = lzo_data.get(data_size..) {\n            if let Ok(eof_marker_size) = parse_lzop_eof_marker(eof_marker_data) {\n                data_size += eof_marker_size;\n                return Ok(data_size);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/matter_ota.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::matter_ota::parse_matter_ota_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Matter OTA firmware\";\n\n/// Matter OTA firmware images always start with these bytes\npub fn matter_ota_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x1e\\xf1\\xee\\x1b\".to_vec()]\n}\n\n/// Validates the Matter OTA header\npub fn matter_ota_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    if let Ok(ota_header) = parse_matter_ota_header(&file_data[offset..]) {\n        result.confidence = CONFIDENCE_HIGH;\n        result.size = ota_header.header_size;\n        result.description = format!(\n            \"{}, total size: {} bytes, tlv header size: {} bytes, vendor id: 0x{:x}, product id: 0x{:x}, version: {}, payload size: {} bytes, digest type: {}, payload digest: {}\",\n            result.description,\n            ota_header.total_size,\n            ota_header.header_size,\n            ota_header.vendor_id,\n            ota_header.product_id,\n            ota_header.version,\n            ota_header.payload_size,\n            ota_header.image_digest_type,\n            ota_header.image_digest,\n        );\n\n        return Ok(result);\n    }\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/mbr.rs",
    "content": "use crate::extractors::mbr::extract_mbr_partitions;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::mbr::parse_mbr_image;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"DOS Master Boot Record\";\n\n/// Offset of magic bytes from the start of the MBR\npub const MAGIC_OFFSET: usize = 0x01FE;\n\n/// MBR always contains these bytes\npub fn mbr_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x55\\xAA\".to_vec()]\n}\n\n/// Validates the MBR header\npub fn mbr_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // This signature is only matched at the beginning of files (see magic.rs), so this check is not strictly necessary\n    if offset == MAGIC_OFFSET {\n        // MBR actually starts this may bytes before the magic bytes\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Do an extraction dry run\n        let dry_run = extract_mbr_partitions(file_data, result.offset, None);\n\n        // If dry run was a success, this is likely a valid MBR\n        if dry_run.success {\n            if let Some(mbr_total_size) = dry_run.size {\n                // Update reported MBR size\n                result.size = mbr_total_size;\n\n                // Parse the MBR header\n                if let Ok(mbr_header) = parse_mbr_image(&file_data[result.offset..]) {\n                    // Examine all reported partitions\n                    for partition in &mbr_header.partitions {\n                        // Carving out partitions starting at offset 0 would result in infinite recurstion during recursive extraction!\n                        if partition.start == result.offset {\n                            result.extraction_declined = true;\n                        }\n\n                        // Add partition info to the description\n                        result.description =\n                            format!(\"{}, partition: {}\", result.description, partition.name);\n                    }\n\n                    // Add total size to the description\n                    result.description =\n                        format!(\"{}, image size: {} bytes\", result.description, result.size);\n\n                    // Everything looks ok\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/mh01.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::signatures::openssl::openssl_crypt_parser;\nuse crate::structures::mh01::parse_mh01_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"D-Link MH01 firmware image\";\n\n/// MH01 firmware images always start with these bytes\npub fn mh01_magic() -> Vec<Vec<u8>> {\n    vec![b\"MH01\".to_vec()]\n}\n\n/// Validates the MH01 header\npub fn mh01_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the firmware header\n    if let Ok(mh01_header) = parse_mh01_header(&file_data[offset..]) {\n        // The encrypted data is expected to be in OpenSSL file format, so parse that too\n        if let Some(crypt_data) = file_data.get(offset + mh01_header.encrypted_data_offset..) {\n            if let Ok(openssl_signature) = openssl_crypt_parser(crypt_data, 0) {\n                result.size = mh01_header.total_size;\n                result.description = format!(\n                    \"{}, signed, encrypted with {}, IV: {}, total size: {} bytes\",\n                    result.description,\n                    openssl_signature.description,\n                    mh01_header.iv,\n                    mh01_header.total_size,\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/ntfs.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::ntfs::parse_ntfs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"NTFS partition\";\n\n/// NTFS partitions start with these bytes\npub fn ntfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xEb\\x52\\x90NTFS\\x20\\x20\\x20\\x20\".to_vec()]\n}\n\n/// Validates the NTFS header\npub fn ntfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(ntfs_header) = parse_ntfs_header(&file_data[offset..]) {\n        // The reported sector count does not include the NTFS boot sector itself\n        result.size = ntfs_header.sector_size * (ntfs_header.sector_count + 1);\n\n        // Simple sanity check on the reported total size\n        if result.size > ntfs_header.sector_size {\n            result.description = format!(\n                \"{}, number of sectors: {}, bytes per sector: {}, total size: {} bytes\",\n                result.description, ntfs_header.sector_count, ntfs_header.sector_size, result.size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/openssl.rs",
    "content": "use crate::common::is_printable_ascii;\nuse crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::openssl::parse_openssl_crypt_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"OpenSSL encryption\";\n\n/// OpenSSL crypto magic\npub fn openssl_crypt_magic() -> Vec<Vec<u8>> {\n    vec![b\"Salted__\".to_vec()]\n}\n\n/// Validate an openssl signature\npub fn openssl_crypt_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(openssl_header) = parse_openssl_crypt_header(&file_data[offset..]) {\n        // Sanity check the salt value\n        if !is_salt_invalid(openssl_header.salt) {\n            // If the magic starts at the beginning of a file, our confidence is a bit higher\n            if offset == 0 {\n                result.confidence = CONFIDENCE_MEDIUM;\n            }\n\n            result.description =\n                format!(\"{}, salt: {:#X}\", result.description, openssl_header.salt);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\n// Returns true if the salt is entirely comprised of NULL and/or ASCII bytes\nfn is_salt_invalid(salt: usize) -> bool {\n    const SALT_LEN: usize = 8;\n\n    let mut bad_byte_count: usize = 0;\n\n    for i in 0..SALT_LEN {\n        let salt_byte = ((salt >> (8 * i)) & 0xFF) as u8;\n\n        if salt_byte == 0 || is_printable_ascii(salt_byte) {\n            bad_byte_count += 1;\n        }\n    }\n\n    bad_byte_count == SALT_LEN\n}\n"
  },
  {
    "path": "src/signatures/packimg.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\nuse crate::structures::packimg::parse_packimg_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"PackImg firmware header\";\n\n/// PackIMG magic bytes\npub fn packimg_magic() -> Vec<Vec<u8>> {\n    vec![b\"--PaCkImGs--\".to_vec()]\n}\n\n/// Parse a PackIMG signature\npub fn packimg_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    let available_data: usize = file_data.len() - offset;\n\n    // Parse the header\n    if let Ok(packimg_header) = parse_packimg_header(&file_data[offset..]) {\n        // Sanity check the reported data size\n        if available_data >= (packimg_header.header_size + packimg_header.data_size) {\n            result.size = packimg_header.header_size;\n            result.description = format!(\n                \"{}, header size: {} bytes, data size: {} bytes\",\n                result.description, packimg_header.header_size, packimg_header.data_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pcap.rs",
    "content": "use crate::extractors::pcap::pcapng_carver;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const PCAPNG_DESCRIPTION: &str = \"Pcap-NG capture file\";\n\n/// Pcap-NG files always start with these bytes\npub fn pcapng_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x0A\\x0D\\x0D\\x0A\".to_vec()]\n}\n\n/// Parses and validates the Pcap-NG file\npub fn pcapng_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: PCAPNG_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do an extraction dry-run\n    let dry_run = pcapng_carver(file_data, offset, None);\n\n    // If dry-run was successful, this is almost certianly a valid pcap-ng file\n    if dry_run.success {\n        if let Some(pcap_size) = dry_run.size {\n            // If this file is just a pcap file, no need to carve it out to yet another file on disk\n            if offset == 0 && pcap_size == file_data.len() {\n                result.extraction_declined = true;\n            }\n\n            // Return parser results\n            result.size = pcap_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pchrom.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\nuse crate::structures::pchrom::parse_pchrom_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Intel serial flash for PCH ROM\";\n\n/// PCH ROM magic bytes\npub fn pch_rom_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x5a\\xa5\\xf0\\x0f\".to_vec()]\n}\n\n/// Validate a PCH ROM signature\npub fn pch_rom_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Magic bytes begin at this offset from the start of file\n    const MAGIC_OFFSET: usize = 16;\n\n    let mut result = SignatureResult {\n        size: 0,\n        offset: 0,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    // Sanity check the offset where the magic bytes were found\n    if offset >= MAGIC_OFFSET {\n        // Set the reported starting offset of this signature\n        result.offset = offset - MAGIC_OFFSET;\n\n        // Parse the header; if this succeeds, assume it is valid\n        if let Ok(pchrom_header) = parse_pchrom_header(&file_data[result.offset..]) {\n            result.size = pchrom_header.header_size + pchrom_header.data_size;\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pdf.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"PDF document\";\n\n/// PDF magic bytes\npub fn pdf_magic() -> Vec<Vec<u8>> {\n    // This assumes a major version of 1\n    vec![b\"%PDF-1.\".to_vec()]\n}\n\n/// Validate a PDF signature\npub fn pdf_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // More than enough data for our needs\n    const MIN_PDF_SIZE: usize = 16;\n\n    const NEWLINE_OFFSET: usize = 8;\n    const MINOR_NUMBER_OFFSET: usize = 7;\n\n    const ASCII_ZERO: u8 = 0x30;\n    const ASCII_NINE: u8 = 0x39;\n    const ASCII_NEWLINE: u8 = 0x0A;\n    const ASCII_PERCENT: u8 = 0x25;\n    const ASCII_CARRIGE_RETURN: u8 = 0x0D;\n\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset,\n        size: 0,\n        ..Default::default()\n    };\n\n    let newline_characters: Vec<u8> = vec![ASCII_NEWLINE, ASCII_CARRIGE_RETURN];\n\n    let pdf_header_start = offset;\n    let pdf_header_end = pdf_header_start + MIN_PDF_SIZE;\n\n    // PDF header is expected to start with something like: %PDF-1.7\\n%\n    if let Some(pdf_header) = file_data.get(pdf_header_start..pdf_header_end) {\n        // Get the minor version number at the expected offset\n        let version_minor: u8 = pdf_header[MINOR_NUMBER_OFFSET];\n\n        // Sanity check the minor version number\n        if (ASCII_ZERO..=ASCII_NINE).contains(&version_minor) {\n            // Update the result description to include the version number\n            result.description = format!(\n                \"{}, version 1.{}\",\n                result.description,\n                version_minor - ASCII_ZERO\n            );\n\n            // Search the remaining bytes for new line characters followed by a percent character\n            for byte in pdf_header[NEWLINE_OFFSET..].iter().copied() {\n                // Any new line or carrige return byte is OK, just keep going\n                if newline_characters.contains(&byte) {\n                    continue;\n                // There should be a percent character\n                } else if byte == ASCII_PERCENT {\n                    return Ok(result);\n                // Anything else is invalid\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pe.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::pe::parse_pe_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Windows PE binary\";\n\n/// Common PE file magics\npub fn pe_magic() -> Vec<Vec<u8>> {\n    /*\n     * This matches the first 16 bytes of a DOS header, from e_magic through e_ss.\n     * Note that these values may differ in some special cases, but these are common ones.\n     */\n    vec![\n        b\"\\x4d\\x5a\\x90\\x00\\x03\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\xff\\xff\\x00\\x00\".to_vec(),\n        b\"\\x4d\\x5a\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n    ]\n}\n\n/// Validate a PE header\npub fn pe_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Parse the PE header\n    if let Ok(pe_header) = parse_pe_header(&file_data[offset..]) {\n        result.description = format!(\n            \"{}, machine type: {}\",\n            result.description, pe_header.machine\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pem.rs",
    "content": "use crate::extractors::pem;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse base64::Engine;\nuse base64::prelude::BASE64_STANDARD;\n\n/// Human readable descriptions\npub const PEM_PUBLIC_KEY_DESCRIPTION: &str = \"PEM public key\";\npub const PEM_PRIVATE_KEY_DESCRIPTION: &str = \"PEM private key\";\npub const PEM_CERTIFICATE_DESCRIPTION: &str = \"PEM certificate\";\n\n/// Public key magic\npub fn pem_public_key_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"-----BEGIN PUBLIC KEY-----\".to_vec(),\n        b\"-----BEGIN RSA PUBLIC KEY-----\".to_vec(),\n        b\"-----BEGIN DSA PUBLIC KEY-----\".to_vec(),\n        b\"-----BEGIN ECDSA PUBLIC KEY-----\".to_vec(),\n    ]\n}\n\n/// Private key magics\npub fn pem_private_key_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"-----BEGIN PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN EC PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN RSA PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN DSA PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN OPENSSH PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN ANY PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN ENCRYPTED PRIVATE KEY-----\".to_vec(),\n        b\"-----BEGIN TSS2 PRIVATE KEY-----\".to_vec(),\n    ]\n}\n\n/// Certificate magic\npub fn pem_certificate_magic() -> Vec<Vec<u8>> {\n    vec![b\"-----BEGIN CERTIFICATE-----\".to_vec()]\n}\n\n/// Validates both PEM certificate and key signatures\npub fn pem_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Enough bytes to uniquely differentiate certs from keys\n    const MIN_PEM_LEN: usize = 26;\n\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    /*\n     * Build a list of magic signatures for public, prvate, and certificate PEMs.\n     * These magics are truncated to MIN_PEM_LEN bytes, which is enough to check if\n     * the matching signature was a public key, private key, or certificate, which is\n     * all we need to know for displaying a useful description string.\n     */\n    let mut public_magics: Vec<Vec<u8>> = vec![];\n    let mut private_magics: Vec<Vec<u8>> = vec![];\n    let mut certificate_magics: Vec<Vec<u8>> = vec![];\n\n    for public_magic in pem_public_key_magic() {\n        public_magics.push(public_magic[0..MIN_PEM_LEN].to_vec());\n    }\n\n    for private_magic in pem_private_key_magic() {\n        private_magics.push(private_magic[0..MIN_PEM_LEN].to_vec());\n    }\n\n    for cert_magic in pem_certificate_magic() {\n        certificate_magics.push(cert_magic[0..MIN_PEM_LEN].to_vec());\n    }\n\n    // Sanity check available data\n    if let Some(pem_magic) = file_data.get(offset..offset + MIN_PEM_LEN) {\n        // Check if this magic is for a PEM cert or a PEM key\n        if public_magics.contains(&pem_magic.to_vec()) {\n            result.description = PEM_PUBLIC_KEY_DESCRIPTION.to_string();\n        } else if private_magics.contains(&pem_magic.to_vec()) {\n            result.description = PEM_PRIVATE_KEY_DESCRIPTION.to_string();\n        } else if certificate_magics.contains(&pem_magic.to_vec()) {\n            result.description = PEM_CERTIFICATE_DESCRIPTION.to_string();\n        } else {\n            // This function will only be called if one of the magics was found, so this should never happen\n            return Err(SignatureError);\n        }\n\n        // Do an extraction dry-run to validate that this PEM file looks sane\n        let dry_run = pem::pem_carver(file_data, offset, None, None);\n        if dry_run.success {\n            if let Some(pem_size) = dry_run.size {\n                // Make sure the PEM data can be base64 decoded\n                if decode_pem_data(&file_data[offset..offset + pem_size]).is_ok() {\n                    // If the file starts and end with this PEM data, no sense in carving it out to another file on disk\n                    if offset == 0 && pem_size == file_data.len() {\n                        result.extraction_declined = true;\n                    }\n\n                    result.size = pem_size;\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Base64 decode PEM file contents\nfn decode_pem_data(pem_file_data: &[u8]) -> Result<usize, SignatureError> {\n    const DELIM: &str = \"--\";\n\n    // Make sure the PEM data can be converted to a string\n    if let Ok(pem_file_string) = String::from_utf8(pem_file_data.to_vec()) {\n        let mut delim_count: usize = 0;\n        let mut base64_string: String = \"\".to_string();\n\n        // Loop through PEM file lines\n        for line in pem_file_string.lines() {\n            // PEM begin and end delimiter strings both start with hyphens\n            if line.starts_with(DELIM) {\n                delim_count += 1;\n\n                // Expect two delimiters: the start, and the end. If we've found both, break.\n                if delim_count == 2 {\n                    break;\n                } else {\n                    continue;\n                }\n            }\n\n            // This is not a delimiter string, append the line to the base64 string to be decoded\n            base64_string.push_str(line);\n        }\n\n        // If we found some text between the delimiters, attempt to base64 decode it\n        if !base64_string.is_empty() {\n            // PEM contents are base64 encoded, they should decode OK; if not, it's a false positive\n            if let Ok(decoded_data) = BASE64_STANDARD.decode(&base64_string) {\n                return Ok(decoded_data.len());\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pjl.rs",
    "content": "use crate::common::get_cstring;\nuse crate::signatures::common::{CONFIDENCE_LOW, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"HP Printer Job Language data\";\n\n/// PJL files typically start with these bytes\npub fn pjl_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x1B%-12345X@PJL\".to_vec()]\n}\n\n/// Parses display info for the PJL\npub fn pjl_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Offset to the first \"@PJL\" string\n    const PJL_COMMANDS_OFFSET: usize = 9;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Get the PJL string data\n    if let Some(pjl_command_data) = file_data.get(offset + PJL_COMMANDS_OFFSET..) {\n        // Pull out a NULL terminated string\n        let mut pjl_text = get_cstring(pjl_command_data);\n        result.size = pjl_text.len();\n\n        if result.size > 0 {\n            // For display, replace new line and carriage return characters with spaces\n            pjl_text = pjl_text.replace(\"\\r\", \" \").replace(\"\\n\", \"\");\n            result.description = format!(\"{}: \\\"{}\\\"\", result.description, pjl_text);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/pkcs_der.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse std::collections::HashMap;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"PKCS DER hash\";\n\n/// Returns a HashMap of the hash types and their associated signature bytes\nfn der_hash_lookups() -> HashMap<String, Vec<u8>> {\n    HashMap::from([\n        (\n            \"MD5\".to_string(),\n            b\"\\x30\\x20\\x30\\x0c\\x06\\x08\\x2a\\x86\\x48\\x86\\xf7\\x0d\\x02\\x05\\x05\\x00\\x04\\x10\".to_vec(),\n        ),\n        (\n            \"SHA1\".to_string(),\n            b\"\\x30\\x21\\x30\\x09\\x06\\x05\\x2b\\x0e\\x03\\x02\\x1a\\x05\\x00\\x04\\x14\".to_vec(),\n        ),\n        (\n            \"SHA256\".to_string(),\n            b\"\\x30\\x31\\x30\\x0d\\x06\\x09\\x60\\x86\\x48\\x01\\x65\\x03\\x04\\x02\\x01\\x05\\x00\\x04\\x20\"\n                .to_vec(),\n        ),\n        (\n            \"SHA384\".to_string(),\n            b\"\\x30\\x41\\x30\\x0d\\x06\\x09\\x60\\x86\\x48\\x01\\x65\\x03\\x04\\x02\\x02\\x05\\x00\\x04\\x30\"\n                .to_vec(),\n        ),\n        (\n            \"SHA512\".to_string(),\n            b\"\\x30\\x51\\x30\\x0d\\x06\\x09\\x60\\x86\\x48\\x01\\x65\\x03\\x04\\x02\\x03\\x05\\x00\\x04\".to_vec(),\n        ),\n    ])\n}\n\n/// DER hash signatures\npub fn der_hash_magic() -> Vec<Vec<u8>> {\n    der_hash_lookups().values().cloned().collect()\n}\n\n/// Validates the DER hash matches\npub fn der_hash_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    for (name, magic) in der_hash_lookups() {\n        let hash_start = offset;\n        let hash_end = hash_start + magic.len();\n\n        if let Some(hash_bytes) = file_data.get(hash_start..hash_end) {\n            if hash_bytes == magic {\n                result.size = magic.len();\n                result.description = format!(\"{}, {}\", result.description, name);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/png.rs",
    "content": "use crate::extractors::png::extract_png_image;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"PNG image\";\n\n/// PNG magic bytes\npub fn png_magic() -> Vec<Vec<u8>> {\n    /*\n     * PNG magic, followed by chunk size and IHDR chunk type.\n     * IHDR must be the first chunk type, and it is a fixed size of 0x0000000D bytes.\n     */\n    vec![b\"\\x89PNG\\x0D\\x0A\\x1A\\x0A\\x00\\x00\\x00\\x0DIHDR\".to_vec()]\n}\n\n/// Validate a PNG signature\npub fn png_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Perform an extraction dry-run\n    let dry_run = extract_png_image(file_data, offset, None);\n\n    // If the dry-run was a success, this is almost certianly a valid PNG\n    if dry_run.success {\n        // Get the total size of the PNG\n        if let Some(png_size) = dry_run.size {\n            // If the start of a file PNG, there's no need to extract it\n            if offset == 0 {\n                result.extraction_declined = true;\n            }\n\n            // Report signature result\n            result.size = png_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/qcow.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::qcow::parse_qcow_header;\n\npub const DESCRIPTION: &str = \"QEMU QCOW Image\";\n\npub fn qcow_magic() -> Vec<Vec<u8>> {\n    vec![b\"QFI\\xFB\".to_vec()]\n}\n\npub fn qcow_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        name: \"qcow\".to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(qcow_header) = parse_qcow_header(file_data) {\n        result.description = format!(\n            \"QEMU QCOW Image, version: {}, storage media size: {:#x} bytes, cluster block size: {:#x} bytes, encryption method: {}\",\n            qcow_header.version,\n            qcow_header.storage_media_size,\n            1 << qcow_header.cluster_block_bits,\n            qcow_header.encryption_method\n        );\n        return Ok(result);\n    };\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/qnx.rs",
    "content": "use crate::signatures::common::{SignatureError, SignatureResult};\nuse crate::structures::qnx::parse_ifs_header;\n\n/// Human readable description\npub const IFS_DESCRIPTION: &str = \"QNX IFS image\";\n\n/// QNX IFS magic bytes\npub fn qnx_ifs_magic() -> Vec<Vec<u8>> {\n    /*\n     * Assumes little endian.\n     * Includes the magic bytes (u32) and version number (u16), which must be 1.\n     */\n    vec![b\"\\xEB\\x7E\\xFF\\x00\\x01\\x00\".to_vec()]\n}\n\n/// Validate a QNX IFS signature\npub fn qnx_ifs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: IFS_DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    let available_data: usize = file_data.len() - offset;\n\n    if let Ok(ifs_header) = parse_ifs_header(&file_data[offset..]) {\n        // Set the total size of this signature\n        result.size = ifs_header.total_size;\n\n        // Sanity check that the total size doesn't exceed the available data size\n        if result.size <= available_data {\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/rar.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::rar::parse_rar_archive_header;\nuse aho_corasick::AhoCorasick;\nuse std::collections::HashMap;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"RAR archive\";\n\n/// RAR magic bytes for both v4 and v5\npub fn rar_magic() -> Vec<Vec<u8>> {\n    vec![b\"Rar!\\x1A\\x07\".to_vec()]\n}\n\n/// Validate RAR signature\npub fn rar_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    let mut extra_description: String = \"\".to_string();\n\n    // Parse the archive header\n    if let Ok(rar_header) = parse_rar_archive_header(&file_data[offset..]) {\n        // Try to locate the RAR end-of-file marker\n        if let Ok(rar_size) = get_rar_size(&file_data[offset..], rar_header.version) {\n            result.size = rar_size;\n            result.confidence = CONFIDENCE_MEDIUM;\n        } else {\n            extra_description = \" (failed to locate RAR EOF)\".to_string();\n        }\n\n        result.description = format!(\n            \"{}, version: {}, total size: {} bytes{}\",\n            result.description, rar_header.version, result.size, extra_description\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n\n/// Determine the size of the RAR file\nfn get_rar_size(file_data: &[u8], rar_version: usize) -> Result<usize, SignatureError> {\n    // EOF markers for Rar v4 and v5\n    let eof_markers: HashMap<usize, Vec<Vec<u8>>> = HashMap::from([\n        (4, vec![b\"\\xC4\\x3D\\x7B\\x00\\x40\\x07\\x00\".to_vec()]),\n        (5, vec![b\"\\x1d\\x77\\x56\\x51\\x03\\x05\\x04\\x00\".to_vec()]),\n    ]);\n\n    if eof_markers.contains_key(&rar_version) {\n        // Select the appropriate EOF marker for this version\n        let eof_marker = &eof_markers[&rar_version];\n\n        // Need to grep the file for the EOF marker\n        let grep = AhoCorasick::new(eof_marker.clone()).unwrap();\n\n        // Search the file data for the EOF marker\n        if let Some(eof_match) = grep.find_overlapping_iter(file_data).next() {\n            // Accept the first match; total size is the start of the EOF marker plus the size of the EOF marker\n            return Ok(eof_match.start() + eof_marker[0].len());\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/riff.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::riff::parse_riff_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"RIFF image\";\n\n/// RIFF file magic bytes\npub fn riff_magic() -> Vec<Vec<u8>> {\n    vec![b\"RIFF\".to_vec()]\n}\n\n/// Validate RIFF signatures\npub fn riff_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Parse the RIFF header\n    if let Ok(riff_header) = parse_riff_header(&file_data[offset..]) {\n        // No sense in extracting an image if the entire file is just the image itself\n        if offset == 0 && riff_header.size == file_data.len() {\n            result.extraction_declined = true;\n        }\n\n        result.size = riff_header.size;\n        result.description = format!(\n            \"{}, encoding type: {}, total size: {} bytes\",\n            result.description, riff_header.chunk_type, result.size\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/romfs.rs",
    "content": "use crate::extractors::romfs::extract_romfs;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::romfs::parse_romfs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"RomFS filesystem\";\n\n/// ROMFS magic bytes\npub fn romfs_magic() -> Vec<Vec<u8>> {\n    vec![b\"-rom1fs-\".to_vec()]\n}\n\n/// Validate a ROMFS signature\npub fn romfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do an extraction dry run\n    let dry_run = extract_romfs(file_data, offset, None);\n\n    // If the dry run was a success, everything should be good to go\n    if dry_run.success {\n        if let Some(romfs_size) = dry_run.size {\n            // Parse the RomFS header to get the volume name\n            if let Ok(romfs_header) = parse_romfs_header(&file_data[offset..]) {\n                // Report the result\n                result.size = romfs_size;\n                result.description = format!(\n                    \"{}, volume name: \\\"{}\\\", total size: {} bytes\",\n                    result.description, romfs_header.volume_name, result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/rsa.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::common;\nuse std::collections::HashMap;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"RSA encrypted session key\";\n\n/// Stores defintions about each RSA key type\n#[derive(Debug, Default, Clone)]\nstruct RSAKeyDefinition {\n    // Magic bytes for this RSA key\n    pub magic: Vec<u8>,\n    // Size of the RSA key, in bits\n    pub key_size: usize,\n    // Offset of the byte indicating if the key can be used for signing/encryption\n    pub usage_offset: usize,\n    // Offset of the 64-bit key ID\n    pub keyid_offset: usize,\n    // HashMap of offsets and expected bytes\n    pub valid_checks: HashMap<usize, Vec<Vec<u8>>>,\n    // Offset of the expected terminator byte, 0xD2\n    pub terminator_offset: usize,\n}\n\n/// Returns a list of RSA key definitions\nfn rsa_key_definitions() -> Vec<RSAKeyDefinition> {\n    vec![\n        // 1024b RSA key\n        RSAKeyDefinition {\n            magic: b\"\\x84\\x8C\\x03\".to_vec(),\n            key_size: 1024,\n            keyid_offset: 3,\n            terminator_offset: 142,\n            usage_offset: 11,\n            valid_checks: HashMap::from([(\n                12,\n                vec![\n                    b\"\\x04\\x00\".to_vec(),\n                    b\"\\x03\\xff\".to_vec(),\n                    b\"\\x03\\xfe\".to_vec(),\n                    b\"\\x03\\xfd\".to_vec(),\n                    b\"\\x03\\xfc\".to_vec(),\n                    b\"\\x03\\xfb\".to_vec(),\n                    b\"\\x03\\xfa\".to_vec(),\n                    b\"\\x03\\xf9\".to_vec(),\n                ],\n            )]),\n        },\n        // 2048b RSA key\n        RSAKeyDefinition {\n            magic: b\"\\x85\\x01\\x0c\\x03\".to_vec(),\n            key_size: 2048,\n            keyid_offset: 4,\n            terminator_offset: 271,\n            usage_offset: 12,\n            valid_checks: HashMap::from([(\n                13,\n                vec![\n                    b\"\\x08\\x00\".to_vec(),\n                    b\"\\x07\\xff\".to_vec(),\n                    b\"\\x07\\xfe\".to_vec(),\n                    b\"\\x07\\xfd\".to_vec(),\n                    b\"\\x07\\xfc\".to_vec(),\n                    b\"\\x07\\xfb\".to_vec(),\n                    b\"\\x07\\xfa\".to_vec(),\n                    b\"\\x07\\xf9\".to_vec(),\n                ],\n            )]),\n        },\n        // 3072b RSA key\n        RSAKeyDefinition {\n            magic: b\"\\x85\\x01\\x8c\\x03\".to_vec(),\n            key_size: 3072,\n            keyid_offset: 4,\n            terminator_offset: 399,\n            usage_offset: 12,\n            valid_checks: HashMap::from([(\n                13,\n                vec![\n                    b\"\\x0c\\x00\".to_vec(),\n                    b\"\\x0b\\xff\".to_vec(),\n                    b\"\\x0b\\xfe\".to_vec(),\n                    b\"\\x0b\\xfd\".to_vec(),\n                    b\"\\x0b\\xfc\".to_vec(),\n                    b\"\\x0b\\xfb\".to_vec(),\n                    b\"\\x0b\\xfa\".to_vec(),\n                    b\"\\x0b\\xf9\".to_vec(),\n                ],\n            )]),\n        },\n        // 4096b RSA key\n        RSAKeyDefinition {\n            magic: b\"\\x85\\x02\\x0c\\x03\".to_vec(),\n            key_size: 4096,\n            keyid_offset: 4,\n            terminator_offset: 527,\n            usage_offset: 12,\n            valid_checks: HashMap::from([(\n                13,\n                vec![\n                    b\"\\x10\\x00\".to_vec(),\n                    b\"\\x0f\\xff\".to_vec(),\n                    b\"\\x0f\\xfe\".to_vec(),\n                    b\"\\x0f\\xfd\".to_vec(),\n                    b\"\\x0f\\xfc\".to_vec(),\n                    b\"\\x0f\\xfb\".to_vec(),\n                    b\"\\x0f\\xfa\".to_vec(),\n                    b\"\\x0f\\xf9\".to_vec(),\n                ],\n            )]),\n        },\n        // 8192b RSA key\n        RSAKeyDefinition {\n            magic: b\"\\x85\\x04\\x0c\\x03\".to_vec(),\n            key_size: 8192,\n            keyid_offset: 4,\n            terminator_offset: 1039,\n            usage_offset: 12,\n            valid_checks: HashMap::from([(\n                13,\n                vec![\n                    b\"\\x20\\x00\".to_vec(),\n                    b\"\\x1f\\xff\".to_vec(),\n                    b\"\\x1f\\xfe\".to_vec(),\n                    b\"\\x1f\\xfd\".to_vec(),\n                    b\"\\x1f\\xfc\".to_vec(),\n                    b\"\\x1f\\xfb\".to_vec(),\n                    b\"\\x1f\\xfa\".to_vec(),\n                    b\"\\x1f\\xf9\".to_vec(),\n                ],\n            )]),\n        },\n    ]\n}\n\n/// RSA crypto magic bytes\npub fn rsa_magic() -> Vec<Vec<u8>> {\n    let mut magics: Vec<Vec<u8>> = vec![];\n\n    for key_definition in rsa_key_definitions() {\n        magics.push(key_definition.magic.clone());\n    }\n\n    magics\n}\n\n/// Validates an RSA encrypted file header\npub fn rsa_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Loop through all the known RSA key types\n    for key_definition in rsa_key_definitions() {\n        let magic_start: usize = offset;\n        let magic_end: usize = magic_start + key_definition.magic.len();\n\n        // Check if these magic bytes belong to this key type\n        if let Some(rsa_magic) = file_data.get(magic_start..magic_end) {\n            if rsa_magic == key_definition.magic {\n                // Parse and validate the key data\n                match rsa_key_parser(&file_data[magic_start..], &key_definition) {\n                    Err(_) => {\n                        break;\n                    }\n                    Ok(key_info) => {\n                        result.size = key_info.data_size;\n                        result.description = format!(\n                            \"{}, {} bits, can sign: {}, can encrypt: {}, key ID: {:#018X}\",\n                            result.description,\n                            key_info.bits,\n                            key_info.can_sign,\n                            key_info.can_encrypt,\n                            key_info.key_id\n                        );\n                        return Ok(result);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Stores info about a validated RSA key\n#[derive(Debug, Default, Clone)]\nstruct RSAKeyInfo {\n    pub bits: usize,\n    pub can_sign: bool,\n    pub can_encrypt: bool,\n    pub key_id: usize,\n    pub data_size: usize,\n}\n\n/// Parse and validate key data based on the provided key definition\nfn rsa_key_parser(\n    raw_data: &[u8],\n    key_definition: &RSAKeyDefinition,\n) -> Result<RSAKeyInfo, SignatureError> {\n    // Expected constant values\n    const TERMINATOR_BYTE: u8 = 0xD2;\n    const ENCRYPT_ONLY: u8 = 2;\n    const SIGN_AND_ENCRYPT: u8 = 1;\n    const VALID_BYTES_SIZE: usize = 2;\n\n    let key_id_structure = vec![(\"id\", \"u64\")];\n\n    let mut result = RSAKeyInfo {\n        ..Default::default()\n    };\n\n    // This is the farthest offset we'll need to index into the key data\n    let key_data_len: usize = key_definition.terminator_offset + std::mem::size_of::<u8>();\n\n    if let Some(key_data) = raw_data.get(0..key_data_len) {\n        // Check the terminator byte\n        if key_data[key_definition.terminator_offset] == TERMINATOR_BYTE {\n            // Get the key ID\n            if let Ok(key_id) = common::parse(\n                &key_data[key_definition.keyid_offset..],\n                &key_id_structure,\n                \"big\",\n            ) {\n                // Report the key ID\n                result.key_id = key_id[\"id\"];\n\n                // Determine if this key can be used to sign or encrypt\n                result.can_encrypt = key_data[key_definition.usage_offset] == ENCRYPT_ONLY;\n                result.can_sign = key_data[key_definition.usage_offset] == SIGN_AND_ENCRYPT;\n\n                // If a key can sign, it can also encrypt\n                if result.can_sign {\n                    result.can_encrypt = true;\n                }\n\n                // A key that can't sign or encrypt would be useless!\n                if result.can_sign || result.can_encrypt {\n                    // Each key has a set of fixed-size bytes that are expected to exist at certian offsets\n                    for (valid_bytes_start, valid_bytes) in\n                        key_definition.valid_checks.clone().into_iter()\n                    {\n                        // Get the bytes to validate; always a size of 2\n                        let valid_bytes_end: usize = valid_bytes_start + VALID_BYTES_SIZE;\n                        let key_bytes = key_data[valid_bytes_start..valid_bytes_end].to_vec();\n\n                        // Check the bytes in the key data against the list of expected bytes\n                        for expected_bytes in valid_bytes {\n                            // Got 'em!\n                            if key_bytes == expected_bytes {\n                                result.bits = key_definition.key_size;\n                                result.data_size = key_data_len;\n                                return Ok(result);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/rtk.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::rtk::parse_rtk_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"RTK firmware header\";\n\n/// RTK firmware images always start with these bytes\npub fn rtk_magic() -> Vec<Vec<u8>> {\n    vec![b\"RTK0\".to_vec()]\n}\n\n/// Validates the RTK header\npub fn rtk_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Note: magic.rs enforces short=true for this signature, so offset will always be 0\n    let available_data = file_data.len() - offset;\n\n    if let Ok(rtk_header) = parse_rtk_header(&file_data[offset..]) {\n        // This firmware header is expected to encompass the entirety of the remaining file data\n        if rtk_header.image_size == available_data {\n            result.size = rtk_header.header_size;\n            result.description = format!(\n                \"{}, header size: {} bytes, image size: {}\",\n                result.description, rtk_header.header_size, rtk_header.image_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/seama.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_LOW, SignatureError, SignatureResult};\nuse crate::structures::seama::parse_seama_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"SEAMA firmware header\";\n\n/// SEAMA magic bytes, big and little endian\npub fn seama_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x5E\\xA3\\xA4\\x17\\x00\\x00\".to_vec(),\n        b\"\\x17\\xA4\\xA3\\x5E\\x00\\x00\".to_vec(),\n    ]\n}\n\n/// Validate SEAMA signatures\npub fn seama_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(seama_header) = parse_seama_header(&file_data[offset..]) {\n        let total_size: usize = seama_header.header_size + seama_header.data_size;\n\n        // Sanity check the reported size\n        if file_data.len() >= (offset + total_size) {\n            result.size = seama_header.header_size;\n            result.description = format!(\n                \"{}, header size: {} bytes, data size: {} bytes\",\n                result.description, seama_header.header_size, seama_header.data_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/sevenzip.rs",
    "content": "use crate::common::crc32;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::sevenzip::parse_7z_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"7-zip archive data\";\n\n/// 7zip magic bytes\npub fn sevenzip_magic() -> Vec<Vec<u8>> {\n    vec![b\"7z\\xbc\\xaf\\x27\\x1c\".to_vec()]\n}\n\n/// Validates 7zip signatures\npub fn sevenzip_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Parse the 7z header\n    if let Ok(sevenzip_header) = parse_7z_header(&file_data[offset..]) {\n        // Calculate the start and end offsets that the next header CRC was calculated over\n        let next_crc_start: usize =\n            offset + sevenzip_header.header_size + sevenzip_header.next_header_offset;\n        let next_crc_end: usize = next_crc_start + sevenzip_header.next_header_size;\n\n        if let Some(crc_data) = file_data.get(next_crc_start..next_crc_end) {\n            // Calculate the next_header CRC\n            let calculated_next_crc: usize = crc32(crc_data) as usize;\n\n            // Validate the next_header CRC\n            if calculated_next_crc == sevenzip_header.next_header_crc {\n                // Calculate total size of the 7zip archive\n                let total_size: usize = sevenzip_header.header_size\n                    + sevenzip_header.next_header_offset\n                    + sevenzip_header.next_header_size;\n\n                // Report signature result\n                return Ok(SignatureResult {\n                    offset,\n                    size: total_size,\n                    confidence: CONFIDENCE_HIGH,\n                    description: format!(\n                        \"{}, version {}.{}, total size: {} bytes\",\n                        DESCRIPTION,\n                        sevenzip_header.major_version,\n                        sevenzip_header.minor_version,\n                        total_size\n                    ),\n                    ..Default::default()\n                });\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/shrs.rs",
    "content": "use crate::signatures::common::{\n    CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::shrs::parse_shrs_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"SHRS encrypted firmware\";\n\n/// SHRS firmware images always start with these bytes\npub fn shrs_magic() -> Vec<Vec<u8>> {\n    vec![b\"SHRS\".to_vec()]\n}\n\n/// Validates the SHRS header\npub fn shrs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    if let Ok(shrs_header) = parse_shrs_header(&file_data[offset..]) {\n        result.size = shrs_header.header_size + shrs_header.data_size;\n        result.description = format!(\n            \"{}, header size: {} bytes, encrypted data size: {} bytes, IV: {}\",\n            result.description,\n            shrs_header.header_size,\n            shrs_header.data_size,\n            hex::encode(shrs_header.iv),\n        );\n\n        if offset == 0 {\n            result.confidence = CONFIDENCE_MEDIUM;\n        }\n\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/squashfs.rs",
    "content": "use crate::common::epoch_to_string;\nuse crate::extractors::squashfs::{\n    squashfs_be_extractor, squashfs_le_extractor, squashfs_v4_be_extractor,\n};\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::squashfs::{parse_squashfs_header, parse_squashfs_uid_entry};\nuse std::collections::HashMap;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"SquashFS file system\";\n\n/// All of the known magic bytes that could indicate the beginning of a SquashFS image\npub fn squashfs_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"sqsh\".to_vec(),\n        b\"hsqs\".to_vec(),\n        b\"sqlz\".to_vec(),\n        b\"qshs\".to_vec(),\n        b\"tqsh\".to_vec(),\n        b\"hsqt\".to_vec(),\n        b\"shsq\".to_vec(),\n    ]\n}\n\n/// Responsible for parsing and validating a suspected SquashFS image header\npub fn squashfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const SQUASHFSV4: usize = 4;\n\n    let squashfs_compression_types = HashMap::from([\n        (0, \"unknown\"),\n        (1, \"gzip\"),\n        (2, \"lzma\"),\n        (3, \"lzo\"),\n        (4, \"xz\"),\n        (5, \"lz4\"),\n        (6, \"zstd\"),\n    ]);\n\n    let mut result = SignatureResult {\n        size: 0,\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let available_data: usize = file_data.len() - offset;\n\n    // Parse the squashfs header\n    if let Ok(squashfs_header) = parse_squashfs_header(&file_data[offset..]) {\n        // Sanity check the reported image size\n        if squashfs_header.image_size <= available_data {\n            /*\n             * To better validate SquashFS images, we want to verify at least some of the SquashFS image contents.\n             * There are situations where the SquashFS header itself is valid and in-tact, but the data is not; for example,\n             * gzipping a SquashFS image often leaves some of the SquashFS data uncompressed, since SquashFS images are already\n             * compressed and the gzip utility realizes that it cannot further compress some sections. This can result in the\n             * contents of the gzipped data containing an uncorrupted copy of the SquashFS header, while some of the SquashFS\n             * image contents are gzipped compressed.\n             *\n             * The easiest field to validate seems to be the UID table pointer, which is an offset in the SquashFS image whre\n             * the UID table resides. This table is just an array of 64-bit pointers, each one pointing to a compressed data block\n             * which contains the actual UIDs. Validate that the UID table pointer is sane, *and* that the first 64-bit pointer\n             * in the UID table is sane.\n             */\n\n            // Get the offset of the UID table, an array of pointers to metadata blocks containing lists of user IDs\n            let uid_table_start: usize = offset + squashfs_header.uid_table_start;\n\n            // Validate that the UID table pointer points to a location after the end of the SquashFS header (it's usually at the end of the image)\n            if uid_table_start > squashfs_header.header_size {\n                // Get the UID table data\n                if let Some(uid_entry_data) = file_data.get(uid_table_start..) {\n                    // Parse one entry from the UID table\n                    if let Ok(uid_entry) = parse_squashfs_uid_entry(\n                        uid_entry_data,\n                        squashfs_header.major_version,\n                        &squashfs_header.endianness,\n                    ) {\n                        // Make sure the first UID table entry is either 0, or falls within the bounds of the SquashFS image data\n                        if (uid_entry == 0)\n                            || (uid_entry > squashfs_header.header_size\n                                && uid_entry <= squashfs_header.image_size)\n                        {\n                            // Format the modified time into something human readable\n                            let create_date = epoch_to_string(squashfs_header.timestamp as u32);\n\n                            // Make sure the compression type is supported\n                            if squashfs_compression_types.contains_key(&squashfs_header.compression)\n                            {\n                                let compression_type_str = squashfs_compression_types\n                                    [&squashfs_header.compression]\n                                    .to_string();\n\n                                // Select the appropriate extractor to use\n                                if squashfs_header.endianness == \"little\" {\n                                    result.preferred_extractor = Some(squashfs_le_extractor());\n                                } else if squashfs_header.major_version == SQUASHFSV4 {\n                                    result.preferred_extractor = Some(squashfs_v4_be_extractor());\n                                } else {\n                                    result.preferred_extractor = Some(squashfs_be_extractor());\n                                }\n\n                                result.size = squashfs_header.image_size;\n                                result.description = format!(\n                                    \"{}, {} endian, version: {}.{}, compression: {}, inode count: {}, block size: {}, image size: {} bytes, created: {}\",\n                                    result.description,\n                                    squashfs_header.endianness,\n                                    squashfs_header.major_version,\n                                    squashfs_header.minor_version,\n                                    compression_type_str,\n                                    squashfs_header.inode_count,\n                                    squashfs_header.block_size,\n                                    squashfs_header.image_size,\n                                    create_date\n                                );\n\n                                return Ok(result);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/srec.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse aho_corasick::AhoCorasick;\n\n/// Human readable descriptions\npub const SREC_DESCRIPTION: &str = \"Motorola S-record\";\npub const SREC_SHORT_DESCRIPTION: &str = \"Motorola S-record (generic)\";\n\n/// Generic, short signature for s-records, should only be matched at the beginning of a file\npub fn srec_short_magic() -> Vec<Vec<u8>> {\n    vec![b\"S0\".to_vec()]\n}\n\n/// This assumes a srec header with the hex encoded string of \"HDR\"\npub fn srec_magic() -> Vec<Vec<u8>> {\n    vec![b\"S00600004844521B\".to_vec()]\n}\n\n/// Validates a SREC signature\npub fn srec_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // \\r and \\n\n    const UNIX_TERMINATING_CHARACTER: u8 = 0x0A;\n    const WINDOWS_TERMINATING_CHARACTER: u8 = 0x0D;\n\n    let mut result = SignatureResult {\n        offset,\n        description: SREC_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let available_data = file_data.len();\n\n    // Srec lines, and hence the last line of an s-record, should end with a new line or line feed\n    let terminating_characters = [WINDOWS_TERMINATING_CHARACTER, UNIX_TERMINATING_CHARACTER];\n\n    // Possible srec footers\n    let srec_footers = vec![b\"\\nS9\", b\"\\nS8\", b\"\\nS7\"];\n\n    // Need to grep for the srec footer to determine total size\n    let grep = AhoCorasick::new(srec_footers.clone()).unwrap();\n\n    // Search for srec footer lines\n    for srec_footer_match in grep.find_overlapping_iter(&file_data[offset..]) {\n        // Assume origin OS is Unix unless proven otherwise\n        let mut os_type: &str = \"Unix\";\n\n        // Start searching for terminating EOF characters after this footer match (all footer signatures are the same size)\n        let mut srec_eof: usize = offset + srec_footer_match.start() + srec_footers[0].len();\n        let mut last_srec_eof = None;\n\n        // Found the start of a possible srec footer line, loop over remianing bytes looking for the line termination\n        while is_offset_safe(available_data, srec_eof, last_srec_eof) {\n            // All srec lines should end in \\n or \\r\\n\n            if terminating_characters.contains(&file_data[srec_eof]) {\n                // Windows systems use \\r\\n\n                if file_data[srec_eof] == WINDOWS_TERMINATING_CHARACTER {\n                    // There should be one more character, a \\n, which is common to both windows and linux implementations\n                    srec_eof += 1;\n                    os_type = \"Windows\";\n                }\n\n                // Sanity check, don't want to index out of bounds\n                if let Some(srec_last_byte) = file_data.get(srec_eof) {\n                    // Last byte should be a line feed (\\n)\n                    if *srec_last_byte == UNIX_TERMINATING_CHARACTER {\n                        // Include the final line feed byte in the size of the s-record\n                        srec_eof += 1;\n\n                        // Report results\n                        result.size = srec_eof - offset;\n                        result.description = format!(\n                            \"{}, origin OS: {}, total size: {} bytes\",\n                            result.description, os_type, result.size\n                        );\n                        return Ok(result);\n                    }\n                }\n\n                // Invalid srec termination, stop searching\n                return Err(SignatureError);\n            }\n\n            // Not a terminating character, go to the next byte in the file\n            last_srec_eof = Some(srec_eof);\n            srec_eof += 1;\n        }\n    }\n\n    // No valid srec footers found\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/svg.rs",
    "content": "use crate::extractors::svg::extract_svg_image;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"SVG image\";\n\n/// SVG magic bytes\npub fn svg_magic() -> Vec<Vec<u8>> {\n    vec![b\"<svg \".to_vec()]\n}\n\n/// Parse an SVG image\npub fn svg_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Perform an extraction dry-run\n    let dry_run = extract_svg_image(file_data, offset, None);\n\n    // If the dry-run was a success, this is probably a valid JPEG file\n    if dry_run.success {\n        // Get the total size of the SVG\n        if let Some(svg_size) = dry_run.size {\n            // If this file starts with SVG data, there's no need to extract it\n            if offset == 0 {\n                result.extraction_declined = true;\n            }\n\n            // Report signature result\n            result.size = svg_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/tarball.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\n\n/// Some tarball constants\nconst TARBALL_BLOCK_SIZE: usize = 512;\nconst TARBALL_MAGIC_OFFSET: usize = 257;\nconst TARBALL_MAGIC_SIZE: usize = 5;\nconst TARBALL_SIZE_OFFSET: usize = 124;\nconst TARBALL_SIZE_LEN: usize = 11;\nconst TARBALL_UNIVERSAL_MAGIC: &[u8; 5] = b\"ustar\";\nconst TARBALL_MIN_EXPECTED_HEADERS: usize = 10;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"POSIX tar archive\";\n\n/// Magic bytes for tarball and GNU tarball file types\npub fn tarball_magic() -> Vec<Vec<u8>> {\n    vec![b\"ustar\\x00\".to_vec(), b\"ustar\\x20\\x20\\x00\".to_vec()]\n}\n\n/// Validate tarball signatures\npub fn tarball_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Stores the running total size of the tarball\n    let mut tarball_total_size: usize = 0;\n\n    // Keep a count of how many tar entry headers were validated\n    let mut valid_header_count: usize = 0;\n\n    // Calculate the actual start of the tarball (header magic does not start at the beginning of a tar entry)\n    let tarball_start_offset = offset - TARBALL_MAGIC_OFFSET;\n\n    // Tarball magic bytes do not start at the beginning of the tarball file\n    let mut next_header_start = tarball_start_offset;\n    let mut previous_header_start = None;\n    let available_data = file_data.len();\n\n    // Loop through available data, processing tarball entry headers\n    while is_offset_safe(available_data, next_header_start, previous_header_start) {\n        // Calculate the end of the next tarball entry data\n        let next_header_end = next_header_start + TARBALL_BLOCK_SIZE;\n\n        // Get the next header's data; this will fail if not enough data is present, protecting\n        // other functions (header_checksum_is_valid, tarball_entry_size) from out-of-bounds access\n        match file_data.get(next_header_start..next_header_end) {\n            None => {\n                break;\n            }\n            Some(tarball_header_block) => {\n                // Bad checksum? Quit processing headers.\n                if !header_checksum_is_valid(tarball_header_block) {\n                    break;\n                }\n\n                // Increment the count of valid tarball headers found\n                valid_header_count += 1;\n\n                // Get the reported size of the next entry header\n                match tarball_entry_size(tarball_header_block) {\n                    Err(_) => {\n                        break;\n                    }\n                    Ok(entry_size) => {\n                        // Update total size count, and next/previous header offsets\n                        tarball_total_size += entry_size;\n                        previous_header_start = Some(next_header_start);\n                        next_header_start += entry_size;\n                    }\n                }\n            }\n        }\n    }\n\n    // We expect that a tarball should be, at a minimum, one block in size\n    if tarball_total_size >= TARBALL_BLOCK_SIZE {\n        // Default confidence is medium\n        let mut confidence = CONFIDENCE_MEDIUM;\n\n        // If more than just a few tarball headers were found and processed successfully, we have pretty high confidence that this isn't a false positive\n        if valid_header_count >= TARBALL_MIN_EXPECTED_HEADERS {\n            confidence = CONFIDENCE_HIGH;\n        }\n\n        return Ok(SignatureResult {\n            description: format!(\"{DESCRIPTION}, file count: {valid_header_count}\"),\n            offset: tarball_start_offset,\n            size: tarball_total_size,\n            confidence,\n            ..Default::default()\n        });\n    }\n\n    Err(SignatureError)\n}\n\n/// Validate a tarball entry checksum\nfn header_checksum_is_valid(header_block: &[u8]) -> bool {\n    const TARBALL_CHECKSUM_START: usize = 148;\n    const TARBALL_CHECKSUM_END: usize = 156;\n\n    let checksum_value_string: &[u8] = &header_block[TARBALL_CHECKSUM_START..TARBALL_CHECKSUM_END];\n    let reported_checksum = tarball_octal(checksum_value_string);\n    let mut sum: usize = 0;\n\n    for (i, header_byte) in header_block.iter().enumerate() {\n        if (TARBALL_CHECKSUM_START..TARBALL_CHECKSUM_END).contains(&i) {\n            sum += 0x20;\n        } else {\n            sum += *header_byte as usize;\n        }\n    }\n\n    sum == reported_checksum\n}\n\n/// Returns the size of a tarball entry, including header and data\nfn tarball_entry_size(tarball_entry_data: &[u8]) -> Result<usize, SignatureError> {\n    // Get the tarball entry's magic bytes\n    let entry_magic: &[u8] =\n        &tarball_entry_data[TARBALL_MAGIC_OFFSET..TARBALL_MAGIC_OFFSET + TARBALL_MAGIC_SIZE];\n\n    // Make sure the magic bytes are valid\n    if entry_magic == TARBALL_UNIVERSAL_MAGIC {\n        // Pull this tarball entry's data size, stored as ASCII octal, out of the header\n        let entry_size_string: &[u8] =\n            &tarball_entry_data[TARBALL_SIZE_OFFSET..TARBALL_SIZE_OFFSET + TARBALL_SIZE_LEN];\n\n        // Convert the ASCII octal to a number\n        let reported_entry_size: usize = tarball_octal(entry_size_string);\n\n        // The actual size of this entry will be the data size, rounded up to the nearest block size, PLUS one block for the entry header\n        let block_count: usize =\n            1 + (reported_entry_size as f32 / TARBALL_BLOCK_SIZE as f32).ceil() as usize;\n\n        // Total size is the total number of blocks times the block size\n        return Ok(block_count * TARBALL_BLOCK_SIZE);\n    }\n\n    Err(SignatureError)\n}\n\n/// Convert octal string to a number\nfn tarball_octal(octal_string: &[u8]) -> usize {\n    let mut num: usize = 0;\n\n    for octal_char in octal_string {\n        // ASCII octal values should be ASCII\n        if *octal_char < 0x30 || *octal_char > 0x39 {\n            break;\n        } else {\n            num *= 8;\n            num = num + (*octal_char as usize) - 0x30;\n        }\n    }\n\n    num\n}\n"
  },
  {
    "path": "src/signatures/tplink.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::tplink::{parse_tplink_header, parse_tplink_rtos_header};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"TP-Link firmware header\";\n\n/// TP-Link firmware headers start with these bytes\npub fn tplink_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x01\\x00\\x00\\x00TP-LINK Technologies\\x00\\x00\\x00\\x00ver. 1.0\".to_vec()]\n}\n\n/// Validates the TP-Link header\npub fn tplink_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(tplink_header) = parse_tplink_header(&file_data[offset..]) {\n        // Fill in size and description\n        result.size = tplink_header.header_size;\n        result.description = format!(\n            \"{}, kernel load address: {:#X}, kernel entry point: {:#X}, header size: {} bytes\",\n            result.description,\n            tplink_header.kernel_load_address,\n            tplink_header.kernel_entry_point,\n            tplink_header.header_size\n        );\n\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n\n/// Human readable description\npub const RTOS_DESCRIPTION: &str = \"TP-Link RTOS firmware\";\n\n/// TP-Link RTOS firmware start with these magic bytes\npub fn tplink_rtos_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x00\\x14\\x2F\\xC0\".to_vec()]\n}\n\n/// Parse and validate TP-Link RTOS firmware header\npub fn tplink_rtos_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    let mut result = SignatureResult {\n        offset,\n        description: RTOS_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Ok(fw_header) = parse_tplink_rtos_header(&file_data[offset..]) {\n        result.description = format!(\n            \"{}, model number: {:X}, hardware version: {:X}.{:X}, header size: {} bytes, total size: {} bytes\",\n            result.description,\n            fw_header.model_number,\n            fw_header.hardware_rev_major,\n            fw_header.hardware_rev_minor,\n            fw_header.header_size,\n            fw_header.total_size,\n        );\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/trx.rs",
    "content": "use crate::extractors::trx::extract_trx_partitions;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::trx::parse_trx_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"TRX firmware image\";\n\n/// TRX magic bytes\npub fn trx_magic() -> Vec<Vec<u8>> {\n    vec![b\"HDR0\".to_vec()]\n}\n\n/// Validates a TRX signature\npub fn trx_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do a dry run to validate the TRX data\n    let dry_run = extract_trx_partitions(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(trx_total_size) = dry_run.size {\n            // Dry run successful, parse the TRX header and return a useful description\n            if let Ok(trx_header) = parse_trx_header(&file_data[offset..]) {\n                result.size = trx_total_size;\n                result.description = format!(\n                    \"{}, version {}, partition count: {}, header size: {} bytes, total size: {} bytes\",\n                    result.description,\n                    trx_header.version,\n                    trx_header.partitions.len(),\n                    trx_header.header_size,\n                    result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/ubi.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::ubi::{\n    parse_ubi_ec_header, parse_ubi_superblock_header, parse_ubi_volume_header,\n};\nuse aho_corasick::AhoCorasick;\nuse std::collections::HashMap;\n\n/// Human readable desciptions\npub const UBI_FS_DESCRIPTION: &str = \"UBIFS image\";\npub const UBI_IMAGE_DESCRIPTION: &str = \"UBI image\";\n\n/// Erase block magic bytes; header version is assumed to be 1\npub fn ubi_magic() -> Vec<Vec<u8>> {\n    vec![b\"UBI#\\x01\".to_vec()]\n}\n\n/// UBI node magic; this matches *any* UBI node, but ubifs_parser ensures that only superblock nodes are reported\npub fn ubifs_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x31\\x18\\x10\\x06\".to_vec()]\n}\n\n/// Validates a UBIFS signature\npub fn ubifs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: UBI_FS_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the UBIFS superblock header\n    if let Ok(sb_header) = parse_ubi_superblock_header(&file_data[offset..]) {\n        // Image size is the number of logical erase blocks times the size of each logical erase block\n        result.size = sb_header.leb_count * sb_header.leb_size;\n        result.description = format!(\"{}, total size: {} bytes\", result.description, result.size);\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n\n/// Validates a UBI signature\npub fn ubi_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: UBI_IMAGE_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the UBI header\n    if let Ok(ubi_header) = parse_ubi_ec_header(&file_data[offset..]) {\n        let data_offset: usize = offset + ubi_header.data_offset;\n        let volume_offset: usize = offset + ubi_header.volume_id_offset;\n\n        // Sanity check the reported volume and data offsets\n        if file_data.len() > data_offset && file_data.len() > volume_offset {\n            // Get the size of the UBI image\n            if let Ok(image_size) = get_ubi_image_size(&file_data[offset..]) {\n                result.size = image_size;\n                result.description = format!(\n                    \"{}, version: {}, image size: {} bytes\",\n                    result.description, ubi_header.version, result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Determines the LEB size and returns the size of the UBI image\nfn get_ubi_image_size(ubi_data: &[u8]) -> Result<usize, SignatureError> {\n    let mut leb_size: usize = 0;\n    let mut block_count: usize = 0;\n    let mut best_leb_match_count: usize = 0;\n    let mut previous_volume_offset: usize = 0;\n    let mut possible_leb_sizes: HashMap<usize, usize> = HashMap::new();\n\n    // Volume magic bytes, version is assumed to be 1\n    let ubi_vol_magic = vec![b\"UBI!\\x01\"];\n\n    let grep = AhoCorasick::new(ubi_vol_magic).unwrap();\n\n    // grep for all volume header magic bytes\n    for magic_match in grep.find_overlapping_iter(ubi_data) {\n        // Offset in the UBI image where this magic match was found\n        let this_volume_offset: usize = magic_match.start();\n\n        // Parse the volume header\n        if parse_ubi_volume_header(&ubi_data[this_volume_offset..]).is_ok() {\n            // Header looks valid, increment the block count\n            block_count += 1;\n\n            // If there was a previous UBI volume header identified, calculate the leb size as the distance between the two volume header\n            if previous_volume_offset != 0 {\n                let this_leb_size = this_volume_offset - previous_volume_offset;\n\n                // Keep track of the calculated leb size, and how many times each possible leb size was found\n                if possible_leb_sizes.contains_key(&this_leb_size) {\n                    possible_leb_sizes\n                        .insert(this_leb_size, possible_leb_sizes[&this_leb_size] + 1);\n                } else {\n                    possible_leb_sizes.insert(this_leb_size, 1);\n                }\n            }\n\n            previous_volume_offset = this_volume_offset;\n        }\n    }\n\n    // Pick the most common leb size\n    for (leb_candidate_size, leb_candidate_count) in possible_leb_sizes.iter() {\n        if *leb_candidate_count > best_leb_match_count {\n            leb_size = *leb_candidate_size;\n            best_leb_match_count = *leb_candidate_count;\n        }\n    }\n\n    // Image size is leb size times the number of blocks\n    if leb_size != 0 && block_count != 0 {\n        return Ok(block_count * leb_size);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/uboot.rs",
    "content": "use crate::common::{get_cstring, is_ascii_number};\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"U-Boot version string\";\n\n/// U-Boot version number magic bytes\npub fn uboot_magic() -> Vec<Vec<u8>> {\n    vec![b\"U-Boot\\x20\".to_vec()]\n}\n\n/// Validates the U-Boot version number magic\npub fn uboot_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    const NUMBER_OFFSET: usize = 7;\n\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    if let Some(expected_number_byte) = file_data.get(offset + NUMBER_OFFSET) {\n        if is_ascii_number(*expected_number_byte) {\n            let uboot_version_string = get_cstring(&file_data[offset + NUMBER_OFFSET..]);\n\n            if !uboot_version_string.is_empty() {\n                result.size = uboot_version_string.len();\n                result.description =\n                    format!(\"{}: {:.100}\", result.description, uboot_version_string);\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/uefi.rs",
    "content": "use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::uefi::{parse_uefi_capsule_header, parse_uefi_volume_header};\n\n/// Human readable descriptions\npub const VOLUME_DESCRIPTION: &str = \"UEFI PI firmware volume\";\npub const CAPSULE_DESCRIPTION: &str = \"UEFI capsule image\";\n\n/// UEFI volume magic bytes\npub fn uefi_volume_magic() -> Vec<Vec<u8>> {\n    vec![b\"_FVH\".to_vec()]\n}\n\n/// UEFI capsule GUIDs\npub fn uefi_capsule_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\xBD\\x86\\x66\\x3B\\x76\\x0D\\x30\\x40\\xB7\\x0E\\xB5\\x51\\x9E\\x2F\\xC5\\xA0\".to_vec(), // EFI capsule GUID\n        b\"\\x8B\\xA6\\x3C\\x4A\\x23\\x77\\xFB\\x48\\x80\\x3D\\x57\\x8C\\xC1\\xFE\\xC4\\x4D\".to_vec(), // EFI2 capsule GUID\n        b\"\\xB9\\x82\\x91\\x53\\xB5\\xAB\\x91\\x43\\xB6\\x9A\\xE3\\xA9\\x43\\xF7\\x2F\\xCC\".to_vec(), // UEFI capsule GUID\n    ]\n}\n\n/// Validates UEFI volume signatures\npub fn uefi_volume_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // The magic signature begins this many bytes from the start of the UEFI volume\n    const UEFI_MAGIC_OFFSET: usize = 40;\n\n    let mut result = SignatureResult {\n        size: 0,\n        offset: 0,\n        description: VOLUME_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    // Volume actually starts UEFI_MAGIC_OFFSET bytes before the magic bytes; make sure there are at least that many bytes preceeding the magic offset\n    if offset >= UEFI_MAGIC_OFFSET {\n        // Set the correct starting offset for this volume\n        result.offset = offset - UEFI_MAGIC_OFFSET;\n\n        // Parse the volume header\n        if let Ok(uefi_volume_header) = parse_uefi_volume_header(&file_data[result.offset..]) {\n            // Make sure the volume size is sane\n            if file_data.len() >= (result.offset + uefi_volume_header.volume_size) {\n                result.size = uefi_volume_header.volume_size;\n                result.description = format!(\n                    \"{}, header CRC: {:#X}, header size: {} bytes, total size: {} bytes\",\n                    result.description,\n                    uefi_volume_header.header_crc as u32,\n                    uefi_volume_header.header_size,\n                    uefi_volume_header.volume_size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Validates UEFI capsule signatures\npub fn uefi_capsule_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        description: CAPSULE_DESCRIPTION.to_string(),\n        offset,\n        size: 0,\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    let available_data: usize = file_data.len() - offset;\n\n    if let Ok(capsule_header) = parse_uefi_capsule_header(&file_data[offset..]) {\n        // Sanity check on header total size field\n        if capsule_header.total_size >= available_data {\n            result.size = capsule_header.total_size;\n            result.description = format!(\n                \"{}, header size: {} bytes, total size: {} bytes\",\n                result.description, capsule_header.header_size, capsule_header.total_size\n            );\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/uimage.rs",
    "content": "use crate::common::epoch_to_string;\nuse crate::extractors::uimage::extract_uimage;\nuse crate::signatures::common::{\n    CONFIDENCE_HIGH, CONFIDENCE_LOW, CONFIDENCE_MEDIUM, SignatureError, SignatureResult,\n};\nuse crate::structures::uimage::parse_uimage_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"uImage firmware image\";\n\n/// uImage magic bytes\npub fn uimage_magic() -> Vec<Vec<u8>> {\n    vec![\n        // Standard uImage magic\n        b\"\\x27\\x05\\x19\\x56\".to_vec(),\n        // Alternate uImage magic (https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=01a1e21863aa30c7a2c252ff06b9aef0cf957970)\n        b\"OKLI\".to_vec(),\n    ]\n}\n\n/// Validates uImage signatures\npub fn uimage_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        size: 0,\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do an extraction dry-run\n    let dry_run = extract_uimage(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(uimage_size) = dry_run.size {\n            // Extraction dry-run ok, parse the header to display some useful info\n            if let Ok(uimage_header) = parse_uimage_header(&file_data[offset..]) {\n                result.size = uimage_size;\n                // Decline extraction if the header CRC does not match, or if the reported data size is 0\n                result.extraction_declined =\n                    !uimage_header.header_crc_valid || uimage_header.data_size == 0;\n                result.description = format!(\n                    \"{}, header size: {} bytes, data size: {} bytes, compression: {}, CPU: {}, OS: {}, image type: {}, load address: {:#X}, entry point: {:#X}, creation time: {}, image name: \\\"{}\\\"\",\n                    result.description,\n                    uimage_header.header_size,\n                    uimage_header.data_size,\n                    uimage_header.compression_type,\n                    uimage_header.cpu_type,\n                    uimage_header.os_type,\n                    uimage_header.image_type,\n                    uimage_header.load_address,\n                    uimage_header.entry_point_address,\n                    epoch_to_string(uimage_header.timestamp as u32),\n                    uimage_header.name\n                );\n                // If the header CRC is invalid, adjust the reported confidence level and report the checksum mis-match\n                if !uimage_header.header_crc_valid {\n                    // If the uImage header was otherwise valid and starts at file offset 0 then we're still fairly confident in the result\n                    if result.offset == 0 {\n                        result.confidence = CONFIDENCE_MEDIUM;\n                    } else {\n                        result.confidence = CONFIDENCE_LOW;\n                    }\n\n                    result.description = format!(\"{}, invalid checksum\", result.description);\n                }\n\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/vxworks.rs",
    "content": "use crate::common::get_cstring;\nuse crate::extractors::vxworks::extract_symbol_table;\nuse crate::signatures::common::{CONFIDENCE_HIGH, CONFIDENCE_LOW, SignatureError, SignatureResult};\n\n/// Human readable descriptions\npub const SYMTAB_DESCRIPTION: &str = \"VxWorks symbol table\";\npub const WIND_KERNEL_DESCRIPTION: &str = \"VxWorks WIND kernel version\";\n\n/// WIND kernel version magic\npub fn wind_kernel_magic() -> Vec<Vec<u8>> {\n    // Magic version string for WIND kernels\n    vec![b\"WIND version \".to_vec()]\n}\n\n/// VxWorks symbol table magic bytes\npub fn symbol_table_magic() -> Vec<Vec<u8>> {\n    // These magic bytes match the type and group fields in the VxWorks symbol table, for both big and little endian targets\n    vec![\n        b\"\\x00\\x00\\x05\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n        b\"\\x00\\x00\\x07\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n        b\"\\x00\\x00\\x09\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n        b\"\\x00\\x05\\x00\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n        b\"\\x00\\x07\\x00\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n        b\"\\x00\\x09\\x00\\x00\\x00\\x00\\x00\\x00\".to_vec(),\n    ]\n}\n\n/// Validates WIND kernel version signatures\npub fn wind_kernel_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // Length of the magic signatures bytes\n    const MAGIC_SIZE: usize = 13;\n\n    let mut result = SignatureResult {\n        offset,\n        description: WIND_KERNEL_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_LOW,\n        ..Default::default()\n    };\n\n    // Want the string that proceeds the magic bytes\n    let version_offset: usize = offset + MAGIC_SIZE;\n\n    if let Some(version_bytes) = file_data.get(version_offset..) {\n        // The wind kernel magic bytes should be followed by a string containing the wind kernel version\n        let version_string = get_cstring(version_bytes);\n\n        // Make sure we got a string\n        if !version_string.is_empty() {\n            result.size = MAGIC_SIZE + version_string.len();\n            result.description = format!(\"{} {}\", result.description, version_string);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Validates VxWorks symbol table signatures\npub fn symbol_table_parser(\n    file_data: &[u8],\n    offset: usize,\n) -> Result<SignatureResult, SignatureError> {\n    // The magic bytes start at this offset from the beginning of the symbol table\n    const MAGIC_OFFSET: usize = 8;\n\n    let mut result = SignatureResult {\n        description: SYMTAB_DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // The magic bytes are not at the beginning of the VxWorks symbol table; sanity check the specified offset\n    if offset >= MAGIC_OFFSET {\n        // Actual start of the symbol table\n        let symtab_start: usize = offset - MAGIC_OFFSET;\n\n        // Do a dry-run extraction of the symbol table\n        let dry_run = extract_symbol_table(file_data, symtab_start, None);\n\n        // If dry run was a success, this is very likely a valid symbol table\n        if dry_run.success {\n            // Get the size of the symbol table from the dry-run\n            if let Some(symtab_size) = dry_run.size {\n                result.size = symtab_size;\n                result.offset = symtab_start;\n                result.description =\n                    format!(\"{}, total size: {} bytes\", result.description, result.size);\n\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/wince.rs",
    "content": "use crate::extractors::wince::wince_dump;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::wince::parse_wince_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Windows CE binary image\";\n\n/// Windows CE magic bytes\npub fn wince_magic() -> Vec<Vec<u8>> {\n    vec![b\"B000FF\\n\".to_vec()]\n}\n\n/// Validates the Windows CE header\npub fn wince_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Successful return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Do an extraction dry-run\n    let dry_run = wince_dump(file_data, offset, None);\n\n    if dry_run.success {\n        if let Some(total_size) = dry_run.size {\n            result.size = total_size;\n\n            // Parse the WinCE header to get some useful info to display\n            if let Ok(wince_header) = parse_wince_header(&file_data[offset..]) {\n                result.description = format!(\n                    \"{}, base address: {:#X}, image size: {} bytes, file size: {} bytes\",\n                    result.description,\n                    wince_header.base_address,\n                    wince_header.image_size,\n                    result.size\n                );\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/xz.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::extractors::lzma::lzma_decompress;\nuse crate::extractors::sevenzip::sevenzip_extractor;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::xz::parse_xz_header;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"XZ compressed data\";\n\n/// XZ magic bytes\npub fn xz_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\xFD\\x37\\x7a\\x58\\x5a\\x00\".to_vec()]\n}\n\n/// Validates XZ signatures\npub fn xz_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let mut next_offset = offset;\n    let mut previous_offset = None;\n    let mut stream_header_count = 0;\n    let available_data = file_data.len() - offset;\n\n    // XZ streams can be concatenated together, need to process them all to determine the size of an XZ file\n    while is_offset_safe(available_data, next_offset, previous_offset) {\n        // Parse the next XZ header to validate the header CRC\n        match parse_xz_header(&file_data[next_offset..]) {\n            Err(_) => break,\n            Ok(_) => {\n                // Header is valid\n                stream_header_count += 1;\n\n                // Do an extraction dry-run to make sure the data decompresses correctly\n                let dry_run = lzma_decompress(file_data, next_offset, None);\n\n                // If dry run was a success, update the offset and size fields\n                if dry_run.success && dry_run.size.is_some() {\n                    previous_offset = Some(next_offset);\n                    next_offset += dry_run.size.unwrap();\n                    result.size += dry_run.size.unwrap();\n                // Else, report that the data is malformed and stop processing XZ streams\n                } else {\n                    // 7z may be able to at least partially extract malformed data streams\n                    result.preferred_extractor = Some(sevenzip_extractor());\n                    result.description = format!(\n                        \"{}, valid header with malformed data stream\",\n                        result.description\n                    );\n                    break;\n                }\n            }\n        }\n    }\n\n    // Return success if at least one valid XZ stream header was found\n    if stream_header_count > 0 {\n        // Only report the total size if we were able to determine the total size\n        if result.size > 0 {\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n        }\n        return Ok(result);\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/yaffs.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};\nuse crate::structures::yaffs::{parse_yaffs_file_header, parse_yaffs_obj_header};\n\n/// Minimum number of expected YAFFS objects in a YAFFS image\nconst MIN_NUMBER_OF_OBJS: usize = 2;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"YAFFSv2 filesystem\";\n\n/// Expect the first YAFFS entry to be either a directory (0x00000003) or file (0x00000001), big or little endian\npub fn yaffs_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x03\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\xFF\\xFF\".to_vec(),\n        b\"\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x01\\xFF\\xFF\".to_vec(),\n        b\"\\x01\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\xFF\\xFF\".to_vec(),\n        b\"\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x01\\xFF\\xFF\".to_vec(),\n    ]\n}\n\n/// Validate a YAFFS signature\npub fn yaffs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Max page size + max spare size\n    const MAX_OBJ_SIZE: usize = 16896;\n    const BIG_ENDIAN_FIRST_BYTE: u8 = 0;\n\n    let mut result = SignatureResult {\n        description: DESCRIPTION.to_string(),\n        offset,\n        size: 0,\n        confidence: CONFIDENCE_MEDIUM,\n        ..Default::default()\n    };\n\n    let mut endianness = \"little\";\n    let available_data = file_data.len();\n    let required_min_offset = offset + (MAX_OBJ_SIZE * MIN_NUMBER_OF_OBJS);\n\n    // Sanity check the amount of available data\n    if is_offset_safe(available_data, required_min_offset, None) {\n        // Detect endianness\n        if file_data[offset] == BIG_ENDIAN_FIRST_BYTE {\n            endianness = \"big\";\n        }\n\n        // Determine the page\n        if let Ok(page_size) = get_page_size(&file_data[offset..]) {\n            // Deterine the chunk size\n            if let Ok(spare_size) = get_spare_size(&file_data[offset..], page_size, endianness) {\n                // Get the total image size\n                if let Ok(image_size) =\n                    get_image_size(&file_data[offset..], page_size, spare_size, endianness)\n                {\n                    result.size = image_size;\n                    result.description = format!(\n                        \"{}, {} endian, page size: {}, spare size: {}, image size: {} bytes\",\n                        result.description, endianness, page_size, spare_size, image_size\n                    );\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n\n/// Returns the detected page size used by the YAFFS image\nfn get_page_size(file_data: &[u8]) -> Result<usize, SignatureError> {\n    // Spare area is expected to start with these bytes, depending on endianess and ECC settings (YAFFS2 only)\n    let spare_magics: Vec<Vec<u8>> = vec![\n        b\"\\x00\\x00\\x10\\x00\".to_vec(),\n        b\"\\x00\\x10\\x00\\x00\".to_vec(),\n        b\"\\xFF\\xFF\\x00\\x00\\x10\\x00\".to_vec(),\n        b\"\\xFF\\xFF\\x00\\x10\\x00\\x00\".to_vec(),\n    ];\n\n    // Valid YAFFS page sizes\n    let page_sizes: Vec<usize> = vec![512, 1024, 2048, 4096, 8192, 16384];\n\n    // Loop through each page size looking for one that is immediately followed by a valid spare data entry.\n    // This is only for YAFFS2! It will fail for YAFFS1 images.\n    for page_size in &page_sizes {\n        for spare_magic in &spare_magics {\n            let start_spare_offset: usize = *page_size;\n            let end_spare_offset: usize = start_spare_offset + spare_magic.len();\n\n            if let Some(spare_magic_candidate) = file_data.get(start_spare_offset..end_spare_offset)\n            {\n                // If this spare data starts with the expected bytes, then we've guessed the page size correctly\n                if spare_magic_candidate == *spare_magic {\n                    return Ok(*page_size);\n                }\n            }\n        }\n    }\n\n    // Nothing valid found\n    Err(SignatureError)\n}\n\n/// Returns the detected spare size of the YAFFS image\nfn get_spare_size(\n    file_data: &[u8],\n    page_size: usize,\n    endianness: &str,\n) -> Result<usize, SignatureError> {\n    // Valid spare sizes\n    let spare_sizes: Vec<usize> = vec![16, 32, 64, 128, 256, 512];\n\n    // Loop through all spare sizes until a valid object header is found\n    // This is only for YAFFS2! It will fail for YAFFS1 images.\n    for spare_size in &spare_sizes {\n        // If this spare size is correct, this should be the location of the next object header\n        let next_obj_offset: usize = (page_size + *spare_size) * MIN_NUMBER_OF_OBJS;\n\n        if let Some(obj_header_data) = file_data.get(next_obj_offset..) {\n            // Attempt to parse this data as a YAFFS object header\n            if parse_yaffs_obj_header(obj_header_data, endianness).is_ok() {\n                return Ok(*spare_size);\n            }\n        }\n    }\n\n    // Nothing valid found\n    Err(SignatureError)\n}\n\n/// Returns the total size of the image, in bytes\nfn get_image_size(\n    file_data: &[u8],\n    page_size: usize,\n    spare_size: usize,\n    endianness: &str,\n) -> Result<usize, SignatureError> {\n    // Object type for files\n    const FILE_TYPE: usize = 1;\n\n    let mut image_size: usize = 0;\n    let mut next_obj_offset: usize = 0;\n    let mut previous_obj_offset = None;\n\n    let available_data = file_data.len();\n    let block_size: usize = page_size + spare_size;\n\n    // Loop through all available data, parsing YAFFS object headers\n    while is_offset_safe(available_data, next_obj_offset, previous_obj_offset) {\n        match file_data.get(next_obj_offset..) {\n            None => {\n                return Err(SignatureError);\n            }\n            Some(obj_data) => {\n                // Parse and validate the object header\n                match parse_yaffs_obj_header(obj_data, endianness) {\n                    Err(_) => {\n                        // This is not necessarily an error; could just be that there is trailing data after the YAFFS image\n                        break;\n                    }\n                    Ok(header) => {\n                        // Each object header takes up at least one block of data\n                        let mut data_blocks: usize = 1;\n\n                        // If this is a file, the file data wil take up additional data blocks\n                        if header.obj_type == FILE_TYPE {\n                            match get_file_block_count(obj_data, page_size, endianness) {\n                                Err(e) => {\n                                    return Err(e);\n                                }\n                                Ok(block_count) => {\n                                    data_blocks += block_count;\n                                }\n                            }\n                        }\n\n                        // Update calculated image size and object header offsets\n                        previous_obj_offset = Some(next_obj_offset);\n                        image_size += data_blocks * block_size;\n                        next_obj_offset = image_size;\n                    }\n                }\n            }\n        }\n    }\n\n    // Sanity check the calculated image size; should be large enough to fit MIN_NUMBER_OF_OBJS, but not extend past EOF\n    if image_size > (block_size * MIN_NUMBER_OF_OBJS) && image_size <= available_data {\n        return Ok(image_size);\n    }\n\n    Err(SignatureError)\n}\n\n/// Returns the number of data blocks used to store file data; this size is only valid for file type objects\nfn get_file_block_count(\n    obj_data: &[u8],\n    page_size: usize,\n    endianness: &str,\n) -> Result<usize, SignatureError> {\n    // parse_yaffs_file_header only parses a portion of the header that we need; the partial structure starts this many bytes into the object data\n    const INFO_STRUCT_START: usize = 268;\n\n    if let Some(file_header_data) = obj_data.get(INFO_STRUCT_START..) {\n        // Parse the partial object header.\n        if let Ok(file_info) = parse_yaffs_file_header(file_header_data, endianness) {\n            // File data is broken up into blocks of page_size bytes\n            let file_block_count: usize =\n                ((file_info.file_size as f64) / (page_size as f64)).ceil() as usize;\n            return Ok(file_block_count);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/zip.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::zip::{parse_eocd_header, parse_zip_header};\nuse aho_corasick::AhoCorasick;\n\n/// Human readable description\npub const DESCRIPTION: &str = \"ZIP archive\";\n\n/// ZIP file entry magic bytes\npub fn zip_magic() -> Vec<Vec<u8>> {\n    vec![b\"PK\\x03\\x04\".to_vec()]\n}\n\n/// Validates a ZIP file entry signature\npub fn zip_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Success return value\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    // Parse the ZIP file header\n    if let Ok(zip_file_header) = parse_zip_header(&file_data[offset..]) {\n        // Locate the end-of-central-directory header, which must come after the zip local file entries\n        match find_zip_eof(file_data, offset) {\n            Ok(zip_info) => {\n                result.size = zip_info.eof - offset;\n                result.description = format!(\n                    \"{}, version: {}.{}, file count: {}, total size: {} bytes\",\n                    result.description,\n                    zip_file_header.version_major,\n                    zip_file_header.version_minor,\n                    zip_info.file_count,\n                    result.size\n                );\n            }\n            // If the ZIP file is corrupted and no EOCD header exists, attempt to parse all the individual ZIP file headers\n            Err(_) => {\n                let available_data = file_data.len() - offset;\n                let mut previous_file_header_offset = None;\n                let mut next_file_header_offset = offset + zip_file_header.total_size;\n\n                while is_offset_safe(\n                    available_data,\n                    next_file_header_offset,\n                    previous_file_header_offset,\n                ) {\n                    match parse_zip_header(&file_data[next_file_header_offset..]) {\n                        Ok(zip_header) => {\n                            previous_file_header_offset = Some(next_file_header_offset);\n                            next_file_header_offset += zip_header.total_size;\n                        }\n                        Err(_) => {\n                            result.size = next_file_header_offset - offset;\n                            result.description = format!(\n                                \"{}, version: {}.{}, missing end-of-central-directory header, total size: {} bytes\",\n                                result.description,\n                                zip_file_header.version_major,\n                                zip_file_header.version_minor,\n                                result.size\n                            );\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        // Only return success if the ZIP file size was identified\n        if result.size > 0 {\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n\npub struct ZipEOCDInfo {\n    pub eof: usize,\n    pub file_count: usize,\n}\n\n/// Need to grep the rest of the file data to locate the end-of-central-directory header, which tells us where the ZIP file ends.\npub fn find_zip_eof(file_data: &[u8], offset: usize) -> Result<ZipEOCDInfo, SignatureError> {\n    // This magic string assumes that the disk_number and central_directory_disk_number are 0\n    const ZIP_EOCD_MAGIC: &[u8; 8] = b\"PK\\x05\\x06\\x00\\x00\\x00\\x00\";\n\n    // Instatiate AhoCorasick search with the ZIP EOCD magic bytes\n    let grep = AhoCorasick::new(vec![ZIP_EOCD_MAGIC]).unwrap();\n\n    // Find all matching ZIP EOCD patterns\n    for eocd_match in grep.find_overlapping_iter(&file_data[offset..]) {\n        // Calculate the start and end of the fixed-size portion of the ZIP EOCD header in the file data\n        let eocd_start: usize = eocd_match.start() + offset;\n\n        // Parse the end-of-central-directory header\n        if let Some(eocd_data) = file_data.get(eocd_start..) {\n            if let Ok(eocd_header) = parse_eocd_header(eocd_data) {\n                return Ok(ZipEOCDInfo {\n                    eof: eocd_start + eocd_header.size,\n                    file_count: eocd_header.file_count,\n                });\n            }\n        }\n    }\n\n    // No valid EOCD record found :(\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/zlib.rs",
    "content": "use crate::extractors::zlib::zlib_decompress;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"Zlib compressed file\";\n\n/// Zlib magic bytes\npub fn zlib_magic() -> Vec<Vec<u8>> {\n    vec![\n        b\"\\x78\\x9c\".to_vec(),\n        b\"\\x78\\xDA\".to_vec(),\n        b\"\\x78\\x5E\".to_vec(),\n    ]\n}\n\n/// Validate a zlib signature\npub fn zlib_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    let mut result = SignatureResult {\n        offset,\n        confidence: CONFIDENCE_HIGH,\n        description: DESCRIPTION.to_string(),\n        ..Default::default()\n    };\n\n    // Decompress the zlib; no output directory specified, dry run only.\n    let decompression_dry_run = zlib_decompress(file_data, offset, None);\n\n    // If the decompression dry run was a success, this signature is almost certianly valid\n    if decompression_dry_run.success {\n        if let Some(zlib_file_size) = decompression_dry_run.size {\n            result.size = zlib_file_size;\n            result.description =\n                format!(\"{}, total size: {} bytes\", result.description, result.size);\n            return Ok(result);\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures/zstd.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::signatures::common::{CONFIDENCE_HIGH, SignatureError, SignatureResult};\nuse crate::structures::zstd::{parse_block_header, parse_zstd_header};\n\n/// Human readable description\npub const DESCRIPTION: &str = \"ZSTD compressed data\";\n\n/// ZSTD magic bytes\npub fn zstd_magic() -> Vec<Vec<u8>> {\n    vec![b\"\\x28\\xb5\\x2f\\xfd\".to_vec()]\n}\n\n/// Validate a ZSTD signature\npub fn zstd_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {\n    // Size of checksum value at EOF\n    const EOF_CHECKSUM_SIZE: usize = 4;\n\n    // More or less arbitrarily chosen\n    const MIN_BLOCK_COUNT: usize = 2;\n\n    let mut result = SignatureResult {\n        offset,\n        description: DESCRIPTION.to_string(),\n        confidence: CONFIDENCE_HIGH,\n        ..Default::default()\n    };\n\n    let available_data = file_data.len();\n\n    // Parse the ZSTD header; this should be safe as the ZSTD magic bytes wouldn't have matched at this offset if nothing was there...\n    if let Ok(zstd_header) = parse_zstd_header(&file_data[offset..]) {\n        /*\n         * The first block header starts immediately after the ZSTD header, BUT there may be optional header fields present.\n         * Must parse the frame header descriptor bit fields to determine total size of the header.\n         */\n        let mut next_block_header_start = offset + zstd_header.fixed_header_size;\n        let mut previous_block_header_start = None;\n\n        // If single segment flag is not set, then window descriptor byte is present in the header\n        if !zstd_header.single_segment_flag {\n            next_block_header_start += 1;\n        }\n\n        // If the dictionary ID flag is non-zero, its value represents the size of the dictionary ID field; else, this field does not exist\n        if zstd_header.dictionary_id_flag == 1 {\n            next_block_header_start += 1;\n        } else if zstd_header.dictionary_id_flag == 2 {\n            next_block_header_start += 2;\n        } else if zstd_header.dictionary_id_flag == 3 {\n            next_block_header_start += 4;\n        }\n\n        /*\n         * If the frame content flag is 0 and the single segment flag is set, then the frame content header field is 1 byte in length;\n         * else, the frame content flag indicates the size of the grame content header field.\n         */\n        if zstd_header.frame_content_flag == 0 && zstd_header.single_segment_flag {\n            next_block_header_start += 1;\n        } else if zstd_header.frame_content_flag == 1 {\n            next_block_header_start += 2;\n        } else if zstd_header.frame_content_flag == 2 {\n            next_block_header_start += 4;\n        } else if zstd_header.frame_content_flag == 3 {\n            next_block_header_start += 8;\n        }\n\n        // Keep a count of how many blocks we've processed\n        let mut block_count: usize = 0;\n\n        // We now know where the first block header starts, loop through all the blocks to determine where the ZSTD data ends\n        while is_offset_safe(\n            available_data,\n            next_block_header_start,\n            previous_block_header_start,\n        ) {\n            // Parse the block header\n            match parse_block_header(&file_data[next_block_header_start..]) {\n                Err(_) => {\n                    break;\n                }\n\n                Ok(block_header) => {\n                    // Block header looks valid, increment block counter\n                    block_count += 1;\n\n                    // The next block header should start at the end of this block; note that the reported block size does not include the size of the block header\n                    previous_block_header_start = Some(next_block_header_start);\n                    next_block_header_start += block_header.header_size + block_header.block_size;\n\n                    // Was this the last block?\n                    if block_header.last_block {\n                        // Update the total size, which is the difference between the end of the last block and the start of the ZSTD header\n                        result.size = next_block_header_start - offset;\n\n                        // If a checksum is included at the end of the block stream, add the checksum size to the total size\n                        if zstd_header.content_checksum_present {\n                            result.size += EOF_CHECKSUM_SIZE;\n                        }\n\n                        // Make sure we've processed more than one block; if so, return Ok, else break and return Err below\n                        if block_count >= MIN_BLOCK_COUNT {\n                            result.description = format!(\n                                \"{}, total size: {} bytes\",\n                                result.description, result.size\n                            );\n                            return Ok(result);\n                        } else {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(SignatureError)\n}\n"
  },
  {
    "path": "src/signatures.rs",
    "content": "//! # File / Data Signatures\n//!\n//! Creating a signature to identify a particular file or data type is composed of two parts:\n//!\n//! 1. Defining the signature's attributes\n//! 2. Writing a parser to parse and validate potential signature matches\n//!\n//! ## Defining a Signature\n//!\n//! Signatures are defined using the `signatures::common::Signature` struct. This structure stores critical information\n//! about a signature, such as the signature name, the magic bytes that are associated with the signature, and which extractor\n//! to use (if any) to extract the data associated with the signature.\n//!\n//! ### Example\n//!\n//! ```ignore\n//! use binwalk::extractors::foobar::foobar_extractor;\n//! use binwalk::signatures::common::Signature;\n//! use binwalk::signatures::foobar::foobar_parser;\n//!\n//! // FooBar file signature\n//! let foobar_signature = Signature {\n//!     // A unique name for the signature, no spaces; signatures can be included/excluded from analysis based on this attribute\n//!     name: \"foobar\".to_string(),\n//!     // Set to true for signatures with very short magic bytes; they will only be matched at file offset 0\n//!     short: false,\n//!     // Offset from the start of the file to the \"magic\" bytes; only really relevant for short signatures\n//!     magic_offset: 0,\n//!     // Most signatures will want to set this to false and let the code in main.rs determine if/when to display\n//!     always_display: false,\n//!     // The magic bytes associated with this signature; there may be more than one set of magic bytes per signature\n//!     magic: vec![b\"\\xF0\\x00\\xBA\\xA2\".to_vec()],\n//!     // This is the parser function to call to validate magic byte matches\n//!     parser: foobar_parser,\n//!     // A short human-readable description of the signature\n//!     description: \"FooBar file\".to_string(),\n//!     // The extractor to use to extract this file/data type\n//!     extractor: Some(foobar_extractor()),\n//! };\n//! ```\n//!\n//! Internally, Binwalk keeps a list of `Signature` definitions in `magic.rs`.\n//!\n//! ## Signature Parsers\n//!\n//! Signature parsers are at the heart of each defined signature. They parse and validate magic matches to ensure accuracy and\n//! determine the total size of the file data (if possible).\n//!\n//! Signature parsers must conform to the `signatures::common::SignatureParser` type definition.\n//! They are provided two arguments: the raw file data, and an offset into the file data where the signature's magic bytes were found.\n//!\n//! Signature parsers must parse and validate the expected signature data, and return either a `signatures::common::SignatureResult`\n//! structure on success, or a `signatures::common::SignatureError` on failure.\n//!\n//! ### Example\n//!\n//! ```ignore\n//! use binwalk::extractors::foobar::extract_foobar_file;\n//! use binwalk::signatures::common::{SignatureResult, SignatureError, CONFIDENCE_HIGH};\n//!\n//! /// This function is responsible for parsing and validating the FooBar file system data whenever the \"magic bytes\"\n//! /// are found inside a file. It is provided access to the entire file data, and an offset into the file data where\n//! /// the magic bytes were found. On success, it will return a signatures::common::SignatureResult structure.\n//! ///\n//! pub fn foobar_parser(file_data: &Vec<u8>, offset: usize) -> Result<SignatureResult, SignatureError> {\n//!    /*\n//!     * This will be returned if the format of the suspected FooBar file system looks correct.\n//!     * We will update it later with more information, but for now just define what is known\n//!     * (the offset where the FooBar file  starts, the human-readable description, and\n//!     * a confidence level), and leave the remaining fields at their defaults.\n//!     *\n//!     * Note that confidence level is chosen somewhat arbitrarily, and should be one of:\n//!     *\n//!     *   - CONFIDENCE_LOW (the default)\n//!     *   - CONFIDENCE_MEDIUM\n//!     *   - CONFIDENCE_HIGH\n//!     *\n//!     * In this case the extractor and header parser (defined elsewhere) validate CRC's, so if those pass,\n//!     * the confidence that this is in fact a FooBar file type is high.\n//!     */\n//!    let mut result = SignatureResult {\n//!         offset: offset,\n//!         description: \"FooBar file\".to_string(),\n//!         confidence: CONFIDENCE_HIGH,\n//!         ..Default::default()\n//!    };\n//!\n//!    /*\n//!     * The internal FooBar file extractor already parses the header and validates the data CRC. By passing it an output\n//!     * directory of None, it will still parse and validate the data, but without performing an extraction.\n//!     */\n//!    let dry_run = extact_foobar_file(file_data, offset, None);\n//!\n//!    // The extractor should have reported success, as well as the total size of the file data\n//!    if dry_run.success == true {\n//!        if let Some(file_size) = dry_run.size {\n//!            // Update the reported size and human-readable description and return the result\n//!            result.size = file_size;\n//!            result.description = format!(\"{}, total size: {} bytes\", result.description, result.size);\n//!            return Ok(result);\n//!        }\n//!    }\n//!\n//!    // Something didn't look right about this file data, it is likely a false positive, so return an error\n//!    return Err(SignatureError);\n//! }\n//! ```\npub mod aes;\npub mod android_bootimg;\npub mod androidsparse;\npub mod apfs;\npub mod arcadyan;\npub mod arj;\npub mod autel;\npub mod binhdr;\npub mod bmp;\npub mod btrfs;\npub mod bzip2;\npub mod cab;\npub mod cfe;\npub mod chk;\npub mod common;\npub mod compressd;\npub mod copyright;\npub mod cpio;\npub mod cramfs;\npub mod csman;\npub mod dahua_zip;\npub mod deb;\npub mod dkbs;\npub mod dlink_tlv;\npub mod dlke;\npub mod dlob;\npub mod dmg;\npub mod dms;\npub mod dpapi;\npub mod dtb;\npub mod dxbc;\npub mod ecos;\npub mod efigpt;\npub mod elf;\npub mod encfw;\npub mod encrpted_img;\npub mod ext;\npub mod fat;\npub mod gif;\npub mod gpg;\npub mod gzip;\npub mod hashes;\npub mod iso9660;\npub mod jboot;\npub mod jffs2;\npub mod jpeg;\npub mod linux;\npub mod logfs;\npub mod luks;\npub mod lz4;\npub mod lzfse;\npub mod lzma;\npub mod lzop;\npub mod matter_ota;\npub mod mbr;\npub mod mh01;\npub mod ntfs;\npub mod openssl;\npub mod packimg;\npub mod pcap;\npub mod pchrom;\npub mod pdf;\npub mod pe;\npub mod pem;\npub mod pjl;\npub mod pkcs_der;\npub mod png;\npub mod qcow;\npub mod qnx;\npub mod rar;\npub mod riff;\npub mod romfs;\npub mod rsa;\npub mod rtk;\npub mod seama;\npub mod sevenzip;\npub mod shrs;\npub mod squashfs;\npub mod srec;\npub mod svg;\npub mod tarball;\npub mod tplink;\npub mod trx;\npub mod ubi;\npub mod uboot;\npub mod uefi;\npub mod uimage;\npub mod vxworks;\npub mod wince;\npub mod xz;\npub mod yaffs;\npub mod zip;\npub mod zlib;\npub mod zstd;\n"
  },
  {
    "path": "src/structures/android_bootimg.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store Android boot image header info\n#[derive(Debug, Default, Clone)]\npub struct AndroidBootImageHeader {\n    pub kernel_size: usize,\n    pub ramdisk_size: usize,\n    pub kernel_load_address: usize,\n    pub ramdisk_load_address: usize,\n}\n\n/// Parses an Android boot image header\npub fn parse_android_bootimg_header(\n    bootimg_data: &[u8],\n) -> Result<AndroidBootImageHeader, StructureError> {\n    let bootimg_structure = vec![\n        (\"magic\", \"u64\"),\n        (\"kernel_size\", \"u32\"),\n        (\"kernel_load_addr\", \"u32\"),\n        (\"ramdisk_size\", \"u32\"),\n        (\"ramdisk_load_addr\", \"u32\"),\n    ];\n\n    // Parse the header\n    if let Ok(bootimg_header) = common::parse(bootimg_data, &bootimg_structure, \"little\") {\n        return Ok(AndroidBootImageHeader {\n            kernel_size: bootimg_header[\"kernel_size\"],\n            kernel_load_address: bootimg_header[\"kernel_load_addr\"],\n            ramdisk_size: bootimg_header[\"ramdisk_size\"],\n            ramdisk_load_address: bootimg_header[\"ramdisk_load_addr\"],\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/androidsparse.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Storage struct for AndroidSparse file header info\n#[derive(Debug, Default, Clone)]\npub struct AndroidSparseHeader {\n    pub major_version: usize,\n    pub minor_version: usize,\n    pub header_size: usize,\n    pub block_size: usize,\n    pub chunk_count: usize,\n}\n\n/// Parse Android Sparse header structures\npub fn parse_android_sparse_header(\n    sparse_data: &[u8],\n) -> Result<AndroidSparseHeader, StructureError> {\n    // Version must be 1.0\n    const MAJOR_VERSION: usize = 1;\n    const MINOR_VERSION: usize = 0;\n\n    // Blocks must be aligned on a 4-byte boundary\n    const BLOCK_ALIGNMENT: usize = 4;\n\n    // Expected value for the reported chunk header size\n    const CHUNK_HEADER_SIZE: usize = 12;\n\n    // Header structure\n    let android_sparse_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"major_version\", \"u16\"),\n        (\"minor_version\", \"u16\"),\n        (\"header_size\", \"u16\"),\n        (\"chunk_header_size\", \"u16\"),\n        (\"block_size\", \"u32\"),\n        (\"block_count\", \"u32\"),\n        (\"total_chunks\", \"u32\"),\n        (\"checksum\", \"u32\"),\n    ];\n\n    let expected_header_size = common::size(&android_sparse_structure);\n\n    // Parse the header\n    if let Ok(header) = common::parse(sparse_data, &android_sparse_structure, \"little\") {\n        // Sanity check header values\n        if header[\"major_version\"] == MAJOR_VERSION\n            && header[\"minor_version\"] == MINOR_VERSION\n            && header[\"header_size\"] == expected_header_size\n            && header[\"chunk_header_size\"] == CHUNK_HEADER_SIZE\n            && (header[\"block_size\"] % BLOCK_ALIGNMENT) == 0\n        {\n            return Ok(AndroidSparseHeader {\n                major_version: header[\"major_version\"],\n                minor_version: header[\"minor_version\"],\n                header_size: header[\"header_size\"],\n                block_size: header[\"block_size\"],\n                chunk_count: header[\"total_chunks\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Storage structure for Android Sparse chunk headers\n#[derive(Debug, Default, Clone)]\npub struct AndroidSparseChunkHeader {\n    pub header_size: usize,\n    pub data_size: usize,\n    pub block_count: usize,\n    pub is_crc: bool,\n    pub is_raw: bool,\n    pub is_fill: bool,\n    pub is_dont_care: bool,\n}\n\n/// Parse the header for an Android Sparse chunk\npub fn parse_android_sparse_chunk_header(\n    chunk_data: &[u8],\n) -> Result<AndroidSparseChunkHeader, StructureError> {\n    // Known chunk types\n    const CHUNK_TYPE_RAW: usize = 0xCAC1;\n    const CHUNK_TYPE_FILL: usize = 0xCAC2;\n    const CHUNK_TYPE_DONT_CARE: usize = 0xCAC3;\n    const CHUNK_TYPE_CRC: usize = 0xCAC4;\n\n    let chunk_structure = vec![\n        (\"chunk_type\", \"u16\"),\n        (\"reserved\", \"u16\"),\n        (\"output_block_count\", \"u32\"),\n        (\"total_size\", \"u32\"),\n    ];\n\n    let mut chonker = AndroidSparseChunkHeader {\n        header_size: common::size(&chunk_structure),\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(chunk_header) = common::parse(chunk_data, &chunk_structure, \"little\") {\n        // Make sure the reserved field is zero\n        if chunk_header[\"reserved\"] == 0 {\n            // Populate the structure values\n            chonker.block_count = chunk_header[\"output_block_count\"];\n            chonker.data_size = chunk_header[\"total_size\"] - chonker.header_size;\n            chonker.is_crc = chunk_header[\"chunk_type\"] == CHUNK_TYPE_CRC;\n            chonker.is_raw = chunk_header[\"chunk_type\"] == CHUNK_TYPE_RAW;\n            chonker.is_fill = chunk_header[\"chunk_type\"] == CHUNK_TYPE_FILL;\n            chonker.is_dont_care = chunk_header[\"chunk_type\"] == CHUNK_TYPE_DONT_CARE;\n\n            // The chunk type must be one of the known chunk types\n            if chonker.is_crc || chonker.is_raw || chonker.is_fill || chonker.is_dont_care {\n                return Ok(chonker);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/apfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Offset of the APFS magic bytes from the start of the APFS image\npub const MAGIC_OFFSET: usize = 0x20;\n\n/// Struct to store APFS header info\n#[derive(Debug, Default, Clone)]\npub struct APFSHeader {\n    pub block_size: usize,\n    pub block_count: usize,\n}\n\n/// Parses an APFS header\npub fn parse_apfs_header(apfs_data: &[u8]) -> Result<APFSHeader, StructureError> {\n    const MAX_FS_COUNT: usize = 100;\n    const FS_COUNT_BLOCK_SIZE: usize = 512;\n\n    // Partial APFS header, just to figure out the size of the image and validate some fields\n    // https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf\n    let apfs_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"block_size\", \"u32\"),\n        (\"block_count\", \"u64\"),\n        (\"nx_features\", \"u64\"),\n        (\"nx_ro_compat_features\", \"u64\"),\n        (\"nx_incompat_features\", \"u64\"),\n        (\"nx_uuid_p1\", \"u64\"),\n        (\"nx_uuid_p2\", \"u64\"),\n        (\"nx_next_oid\", \"u64\"),\n        (\"nx_next_xid\", \"u64\"),\n        (\"nx_xp_desc_blocks\", \"u32\"),\n        (\"nx_xp_data_blocks\", \"u32\"),\n        (\"nx_xp_desc_base\", \"u64\"),\n        (\"nx_xp_data_base\", \"u64\"),\n        (\"nx_xp_desc_next\", \"u32\"),\n        (\"nx_xp_data_next\", \"u32\"),\n        (\"nx_xp_desc_index\", \"u32\"),\n        (\"nx_xp_desc_len\", \"u32\"),\n        (\"nx_xp_data_index\", \"u32\"),\n        (\"nx_xp_data_len\", \"u32\"),\n        (\"nx_spaceman_oid\", \"u64\"),\n        (\"nx_omap_oid\", \"u64\"),\n        (\"nx_reaper_oid\", \"u64\"),\n        (\"nx_xp_test_type\", \"u32\"),\n        (\"nx_xp_max_file_systems\", \"u32\"),\n    ];\n\n    // Expected values of superblock flag fields\n    let allowed_feature_flags: Vec<usize> = vec![0, 1, 2, 3];\n    let allowed_incompat_flags: Vec<usize> = vec![0, 1, 2, 3, 0x100, 0x101, 0x102, 0x103];\n    let allowed_ro_compat_flags: Vec<usize> = vec![0];\n\n    let apfs_struct_start = MAGIC_OFFSET;\n    let apfs_struct_end = apfs_struct_start + common::size(&apfs_structure);\n\n    // Parse the header\n    if let Some(apfs_structure_data) = apfs_data.get(apfs_struct_start..apfs_struct_end) {\n        if let Ok(apfs_header) = common::parse(apfs_structure_data, &apfs_structure, \"little\") {\n            // Simple sanity check on the reported block data\n            if apfs_header[\"block_size\"] != 0 && apfs_header[\"block_count\"] != 0 {\n                // Sanity check the feature flags\n                if allowed_feature_flags.contains(&apfs_header[\"nx_features\"])\n                    && allowed_ro_compat_flags.contains(&apfs_header[\"nx_ro_compat_features\"])\n                    && allowed_incompat_flags.contains(&apfs_header[\"nx_incompat_features\"])\n                {\n                    // The test_type field *must* be NULL\n                    if apfs_header[\"nx_xp_test_type\"] == 0 {\n                        // Calculate the file system count; this is max_file_systems divided by 512, rounded up to nearest whole\n                        let fs_count = ((apfs_header[\"nx_xp_max_file_systems\"] as f32)\n                            / (FS_COUNT_BLOCK_SIZE as f32))\n                            .ceil() as usize;\n\n                        // Sanity check the file system count\n                        if fs_count > 0 && fs_count <= MAX_FS_COUNT {\n                            return Ok(APFSHeader {\n                                block_size: apfs_header[\"block_size\"],\n                                block_count: apfs_header[\"block_count\"],\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/arj.rs",
    "content": "use crate::common::{epoch_to_string, get_cstring};\nuse crate::structures::common;\nuse crate::structures::common::StructureError;\n\n#[derive(Debug, Default, Clone)]\npub struct ARJHeader {\n    pub header_size: usize,\n    pub version: u8,\n    pub min_version: u8,\n    pub flags: String,\n    pub host_os: String,\n    pub compression_method: String,\n    pub file_type: String,\n    pub original_name: String,\n    pub original_file_date: String,\n    pub compressed_file_size: i32,\n    pub uncompressed_file_size: i32,\n}\n\npub fn parse_arj_header(arj_data: &[u8]) -> Result<ARJHeader, StructureError> {\n    // ARJ header structure (https://www.fileformat.info/format/arj/corion.htm)\n    let arj_header_structure = vec![\n        (\"magic\", \"u16\"),               // offset 0x00\n        (\"basic_header_size\", \"u16\"),   // offset 0x02\n        (\"extra_header_size\", \"u8\"),    // offset 0x04\n        (\"archiver_version\", \"u8\"),     // offset 0x05\n        (\"min_version\", \"u8\"),          // offset 0x06\n        (\"host_os\", \"u8\"),              // offset 0x07\n        (\"internal_flags\", \"u8\"),       // offset 0x08\n        (\"compression_method\", \"u8\"),   // offset 0x09\n        (\"file_type\", \"u8\"),            // offset 0x0A\n        (\"reserved1\", \"u8\"),            // offset 0x0B\n        (\"datetime_file\", \"u32\"),       // offset 0x0C\n        (\"compressed_filesize\", \"u32\"), // offset 0x10\n        (\"original_filesize\", \"u32\"),   // offset 0x14\n    ];\n\n    if let Ok(arj_header) = common::parse(arj_data, &arj_header_structure, \"little\") {\n        // check the version information in the header\n        if !(1..=16).contains(&arj_header[\"archiver_version\"])\n            || !(1..=16).contains(&arj_header[\"min_version\"])\n            || arj_header[\"archiver_version\"] < arj_header[\"min_version\"]\n        {\n            return Err(StructureError);\n        }\n        let mut flags = if arj_header[\"internal_flags\"] & 0x01 != 0 {\n            \"password\".to_string()\n        } else {\n            \"no password\".to_string()\n        };\n        if arj_header[\"internal_flags\"] & 0x04 != 0 {\n            flags = format!(\"{flags}|multi-volume\");\n        }\n        // let file_start_pos_is_available =  arj_header[\"internal_flags\"] & 0x08 != 0;\n        if arj_header[\"internal_flags\"] & 0x10 != 0 {\n            flags = format!(\"{flags}|slash-switched\");\n        }\n        if arj_header[\"internal_flags\"] & 0x20 != 0 {\n            flags = format!(\"{flags}|backup\");\n        }\n        let host_os = match &arj_header[\"host_os\"] {\n            0 => \"MS-DOS\".to_string(),\n            1 => \"PRIMOS\".to_string(),\n            2 => \"UNIX\".to_string(),\n            3 => \"AMIGA\".to_string(),\n            4 => \"MAX-OS\".to_string(),\n            5 => \"OS/2\".to_string(),\n            6 => \"APPLE GS\".to_string(),\n            7 => \"ATARI ST\".to_string(),\n            8 => \"NeXT\".to_string(),\n            9 => \"VAX VMS\".to_string(),\n            _ => return Err(StructureError),\n        };\n        let compression_method = match &arj_header[\"compression_method\"] {\n            0 => \"stored\".to_string(),\n            1 => \"compressed most\".to_string(),\n            2 => \"compressed\".to_string(),\n            3 => \"compressed faster\".to_string(),\n            4 => \"compressed fastest\".to_string(),\n            _ => return Err(StructureError),\n        };\n        let file_type = match &arj_header[\"file_type\"] {\n            0 => \"binary\".to_string(),\n            1 => \"7-bit text\".to_string(),\n            2 => \"comment header\".to_string(),\n            3 => \"directory\".to_string(),\n            4 => \"volume label\".to_string(),\n            _ => return Err(StructureError),\n        };\n        let compressed_file_size = arj_header[\"compressed_filesize\"] as i32;\n        if compressed_file_size < 0 {\n            return Err(StructureError);\n        }\n        let uncompressed_file_size = arj_header[\"original_filesize\"] as i32;\n        if uncompressed_file_size < 0 {\n            return Err(StructureError);\n        }\n\n        let header_size = arj_header[\"extra_header_size\"];\n        let original_name = if let Some(data) = arj_data.get(header_size + 4..) {\n            get_cstring(data)\n        } else {\n            \"\".to_string()\n        };\n\n        return Ok(ARJHeader {\n            header_size,\n            version: arj_header[\"archiver_version\"] as u8,\n            min_version: arj_header[\"min_version\"] as u8,\n            flags,\n            host_os,\n            compression_method,\n            file_type,\n            original_name,\n            original_file_date: epoch_to_string(arj_header[\"datetime_file\"] as u32),\n            compressed_file_size,\n            uncompressed_file_size,\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/autel.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store Autel ECC header info\n#[derive(Debug, Default, Clone)]\npub struct AutelECCHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parses an Autel header\npub fn parse_autel_header(autel_data: &[u8]) -> Result<AutelECCHeader, StructureError> {\n    const EXPECTED_HEADER_SIZE: usize = 0x20;\n    const COPYRIGHT_SIZE: usize = 16;\n    const EXPECTED_COPYRIGHT_STRING: &str = \"Copyright Autel\";\n\n    let autel_ecc_structure = vec![\n        (\"magic\", \"u64\"),\n        (\"data_size\", \"u32\"),\n        (\"header_size\", \"u32\"),\n        // Followed by 16-byte copyright string\n    ];\n\n    // Parse the header\n    if let Ok(autel_header) = common::parse(autel_data, &autel_ecc_structure, \"little\") {\n        // Sanity check the reported header size\n        if autel_header[\"header_size\"] == EXPECTED_HEADER_SIZE {\n            let copyright_start = common::size(&autel_ecc_structure);\n            let copyright_end = copyright_start + COPYRIGHT_SIZE;\n\n            // Get the copyright string contained in the header\n            if let Some(copyright_bytes) = autel_data.get(copyright_start..copyright_end) {\n                let copyright_string = get_cstring(copyright_bytes);\n\n                // Sanity check the copyright string value\n                if copyright_string == EXPECTED_COPYRIGHT_STRING {\n                    return Ok(AutelECCHeader {\n                        data_size: autel_header[\"data_size\"],\n                        header_size: autel_header[\"header_size\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/binhdr.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Struct to store BIN header info\npub struct BINHeader {\n    pub board_id: String,\n    pub hardware_revision: String,\n    pub firmware_version_major: usize,\n    pub firmware_version_minor: usize,\n}\n\n/// Parses a BIN header\npub fn parse_bin_header(bin_hdr_data: &[u8]) -> Result<BINHeader, StructureError> {\n    // The data strcuture is preceeded by a 4-byte board ID string\n    const STRUCTURE_OFFSET: usize = 4;\n\n    let bin_hdr_structure = vec![\n        (\"reserved1\", \"u32\"),\n        (\"build_date\", \"u32\"),\n        (\"firmware_version_major\", \"u8\"),\n        (\"firmware_version_minor\", \"u8\"),\n        (\"magic\", \"u32\"),\n        (\"hardware_id\", \"u8\"),\n        (\"reserved2\", \"u24\"),\n        (\"reserved3\", \"u64\"),\n    ];\n\n    let known_hardware_ids: HashMap<usize, &str> =\n        HashMap::from([(0, \"4702\"), (1, \"4712\"), (2, \"4712L\"), (3, \"4704\")]);\n\n    // Parse the header\n    if let Some(structure_data) = bin_hdr_data.get(STRUCTURE_OFFSET..) {\n        if let Ok(header) = common::parse(structure_data, &bin_hdr_structure, \"little\") {\n            // Make sure the reserved fields are NULL\n            if header[\"reserved1\"] == 0 && header[\"reserved2\"] == 0 && header[\"reserved3\"] == 0 {\n                // Make sure the reported hardware ID is valid\n                if known_hardware_ids.contains_key(&header[\"hardware_id\"]) {\n                    // Get the board ID string, which immediately preceeds the data structure\n                    if let Some(board_id_bytes) = bin_hdr_data.get(0..STRUCTURE_OFFSET) {\n                        let board_id = get_cstring(board_id_bytes);\n\n                        // The board ID string should be 4 bytes in length\n                        if board_id.len() == STRUCTURE_OFFSET {\n                            return Ok(BINHeader {\n                                board_id,\n                                hardware_revision: known_hardware_ids[&header[\"hardware_id\"]]\n                                    .to_string(),\n                                firmware_version_major: header[\"firmware_version_major\"],\n                                firmware_version_minor: header[\"firmware_version_minor\"],\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/bmp.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n#[derive(Debug, Default, Clone)]\npub struct BMPFileHeader {\n    pub size: usize,\n    pub bitmap_bits_offset: usize,\n}\n\npub fn parse_bmp_file_header(bmp_data: &[u8]) -> Result<BMPFileHeader, StructureError> {\n    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader\n    let bmp_header_structure = vec![\n        (\"bfType\", \"u16\"),\n        (\"bfSize\", \"u32\"),\n        (\"bfReserved1\", \"u16\"),\n        (\"bfReserved2\", \"u16\"),\n        (\"bfOffBits\", \"u32\"),\n    ];\n\n    if let Ok(bmp_header) = common::parse(bmp_data, &bmp_header_structure, \"little\") {\n        let bmp_data_size = bmp_data.len();\n\n        // The BMP file size cannot be bigger than bmp_data\n        if bmp_data_size < bmp_header[\"bfSize\"] {\n            return Err(StructureError);\n        }\n\n        // The file size cannot be 0\n        if bmp_header[\"bfSize\"] == 0 {\n            return Err(StructureError);\n        }\n\n        // The offset cannot be 0\n        if bmp_header[\"bfOffBits\"] == 0 {\n            return Err(StructureError);\n        }\n\n        // The offset cannot be bigger than the file\n        if bmp_header[\"bfOffBits\"] > bmp_data_size {\n            return Err(StructureError);\n        }\n\n        // If everything is Ok so far, return a BMPFileHeader\n        return Ok(BMPFileHeader {\n            size: bmp_header[\"bfSize\"],\n            bitmap_bits_offset: bmp_header[\"bfOffBits\"],\n        });\n    }\n\n    Err(StructureError)\n}\n\n// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header\n// \"The number of bytes required by the structure. Applications should use this member to determine which bitmap information header structure is being used.\"\npub fn get_dib_header_size(bmp_data: &[u8]) -> Result<usize, StructureError> {\n    let valid_header_sizes = [\n        12,  // BITMAPCOREHEADER\n        40,  // BITMAPINFOHEADER\n        108, // BITMAPV4HEADER\n        124,\n    ];\n\n    let header_size = u32::from_le_bytes(bmp_data[..4].try_into().unwrap());\n\n    if !valid_header_sizes.contains(&header_size) {\n        return Err(StructureError);\n    }\n\n    Ok(header_size as usize)\n}\n"
  },
  {
    "path": "src/structures/btrfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse crc32c::crc32c;\n\n/// Struct to store BTRFS super block info\n#[derive(Debug, Default, Clone)]\npub struct BTRFSHeader {\n    pub bytes_used: usize,\n    pub total_size: usize,\n    pub leaf_size: usize,\n    pub node_size: usize,\n    pub stripe_size: usize,\n    pub sector_size: usize,\n}\n\n/// Parse and validate a BTRFS super block\npub fn parse_btrfs_header(btrfs_data: &[u8]) -> Result<BTRFSHeader, StructureError> {\n    const SUPERBLOCK_OFFSET: usize = 0x10000;\n    const SUPERBLOCK_END: usize = SUPERBLOCK_OFFSET + 0x1000;\n    const CRC_START: usize = 0x20;\n\n    // Partial BTRFS superblock structure for obtaining image size and CRC validation\n    // https://archive.kernel.org/oldwiki/btrfs.wiki.kernel.org/index.php/On-disk_Format.html#Superblock\n    let btrfs_structure = vec![\n        (\"header_checksum\", \"u32\"),\n        (\"unused1\", \"u32\"),\n        (\"unused2\", \"u64\"),\n        (\"unused3\", \"u64\"),\n        (\"unused4\", \"u64\"),\n        (\"uuid_p1\", \"u64\"),\n        (\"uuid_p2\", \"u64\"),\n        (\"block_phys_addr\", \"u64\"),\n        (\"flags\", \"u64\"),\n        (\"magic\", \"u64\"),\n        (\"generation\", \"u64\"),\n        (\"root_tree_address\", \"u64\"),\n        (\"chunk_tree_address\", \"u64\"),\n        (\"log_tree_address\", \"u64\"),\n        (\"log_root_transid\", \"u64\"),\n        (\"total_bytes\", \"u64\"),\n        (\"bytes_used\", \"u64\"),\n        (\"root_dir_objid\", \"u64\"),\n        (\"num_devices\", \"u64\"),\n        (\"sector_size\", \"u32\"),\n        (\"node_size\", \"u32\"),\n        (\"leaf_size\", \"u32\"),\n        (\"stripe_size\", \"u32\"),\n    ];\n\n    // Parse the header\n    if let Some(btrfs_header_data) = btrfs_data.get(SUPERBLOCK_OFFSET..SUPERBLOCK_END) {\n        if let Ok(btrfs_header) = common::parse(btrfs_header_data, &btrfs_structure, \"little\") {\n            // Validate the superblock CRC\n            if btrfs_header[\"header_checksum\"] == (crc32c(&btrfs_header_data[CRC_START..]) as usize)\n            {\n                return Ok(BTRFSHeader {\n                    sector_size: btrfs_header[\"sector_size\"],\n                    node_size: btrfs_header[\"node_size\"],\n                    leaf_size: btrfs_header[\"leaf_size\"],\n                    stripe_size: btrfs_header[\"stripe_size\"],\n                    bytes_used: btrfs_header[\"bytes_used\"],\n                    total_size: btrfs_header[\"total_bytes\"],\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/cab.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores CAB header info\n#[derive(Debug, Default, Clone)]\npub struct CabinetHeader {\n    pub total_size: usize,\n    pub header_size: usize,\n    pub file_count: usize,\n    pub folder_count: usize,\n}\n\n/// Parse a CAB file header\npub fn parse_cab_header(header_data: &[u8]) -> Result<CabinetHeader, StructureError> {\n    // CAB files must be version 1.3\n    const MAJOR_VERSION: usize = 1;\n    const MINOR_VERSION: usize = 3;\n\n    const CAB_STRUCT_SIZE: usize = 40;\n    const CAB_EXTRA_STRUCT_SIZE: usize = 20;\n    const FLAG_EXTRA_DATA_PRESENT: usize = 4;\n\n    let cab_header_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"reserved1\", \"u32\"),\n        (\"size\", \"u32\"),\n        (\"reserved2\", \"u32\"),\n        (\"first_file_offset\", \"u32\"),\n        (\"reserved3\", \"u32\"),\n        (\"minor_version\", \"u8\"),\n        (\"major_version\", \"u8\"),\n        (\"folder_count\", \"u16\"),\n        (\"file_count\", \"u16\"),\n        (\"flags\", \"u16\"),\n        (\"id\", \"u16\"),\n        (\"set_number\", \"u16\"),\n        (\"extra_header_size\", \"u16\"),\n        (\"cbCFFolder\", \"u8\"),\n        (\"cbCFData\", \"u8\"),\n    ];\n\n    let cab_extra_header_structure = vec![\n        (\"unknown1\", \"u32\"),\n        (\"data_offset\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"unknown2\", \"u32\"),\n        (\"unknown3\", \"u32\"),\n    ];\n\n    let mut header_info = CabinetHeader {\n        header_size: CAB_STRUCT_SIZE,\n        ..Default::default()\n    };\n\n    // Parse the CAB header\n    if let Ok(cab_header) = common::parse(header_data, &cab_header_structure, \"little\") {\n        // All reserved fields must be 0\n        if cab_header[\"reserved1\"] == 0\n            && cab_header[\"reserved2\"] == 0\n            && cab_header[\"reserved3\"] == 0\n        {\n            // Version must be 1.3\n            if cab_header[\"major_version\"] == MAJOR_VERSION\n                && cab_header[\"minor_version\"] == MINOR_VERSION\n            {\n                // Update the CabinetHeader fields\n                header_info.total_size = cab_header[\"size\"];\n                header_info.file_count = cab_header[\"file_count\"];\n                header_info.folder_count = cab_header[\"folder_count\"];\n\n                // Assume everything is *not* ok, until proven otherwise\n                let mut everything_ok: bool = false;\n\n                // If the extra data flag was set, we need to parse the extra data header to get the size of the extra data\n                if (cab_header[\"flags\"] & FLAG_EXTRA_DATA_PRESENT) != 0\n                    && cab_header[\"extra_header_size\"] == CAB_EXTRA_STRUCT_SIZE\n                {\n                    // Calclate the start and end of the extra header\n                    let extra_header_start: usize = CAB_STRUCT_SIZE;\n                    let extra_header_end: usize = extra_header_start + CAB_EXTRA_STRUCT_SIZE;\n\n                    // Get the extra header raw data\n                    if let Some(extra_header_data) =\n                        header_data.get(extra_header_start..extra_header_end)\n                    {\n                        // Parse the extra header\n                        if let Ok(extra_header) =\n                            common::parse(extra_header_data, &cab_extra_header_structure, \"little\")\n                        {\n                            // The extra data is expected to come immediately after the data specified in the main CAB header\n                            if extra_header[\"data_offset\"] == cab_header[\"size\"] {\n                                // Update the CAB file size to include the extra data\n                                header_info.total_size += extra_header[\"data_size\"];\n                                everything_ok = true;\n                            }\n                        }\n                    }\n                } else {\n                    everything_ok = true;\n                }\n\n                // If everything checked out OK, return the result\n                if everything_ok {\n                    return Ok(header_info);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/chk.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Storage struct for CHK header info\n#[derive(Debug, Clone, Default)]\npub struct CHKHeader {\n    pub header_size: usize,\n    pub kernel_size: usize,\n    pub rootfs_size: usize,\n    pub board_id: String,\n}\n\n/// Parse a CHK firmware header\npub fn parse_chk_header(header_data: &[u8]) -> Result<CHKHeader, StructureError> {\n    // Somewhat arbitrarily chosen\n    const MAX_EXPECTED_HEADER_SIZE: usize = 100;\n\n    let chk_header_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"header_size\", \"u32\"),\n        (\"unknown\", \"u64\"),\n        (\"kernel_checksum\", \"u32\"),\n        (\"rootfs_checksum\", \"u32\"),\n        (\"rootfs_size\", \"u32\"),\n        (\"kernel_size\", \"u32\"),\n        (\"image_checksum\", \"u32\"),\n        (\"header_checksum\", \"u32\"),\n        // Board ID string follows\n    ];\n\n    // Size of the fixed-length portion of the header structure\n    let struct_size: usize = common::size(&chk_header_structure);\n\n    // Parse the CHK header\n    if let Ok(chk_header) = common::parse(header_data, &chk_header_structure, \"big\") {\n        // Validate the reported header size\n        if chk_header[\"header_size\"] > struct_size\n            && chk_header[\"header_size\"] <= MAX_EXPECTED_HEADER_SIZE\n        {\n            // Read in the board ID string which immediately follows the fixed size structure and extends to the end of the header\n            let board_id_start: usize = struct_size;\n            let board_id_end: usize = chk_header[\"header_size\"];\n\n            if let Some(board_id_raw_bytes) = header_data.get(board_id_start..board_id_end) {\n                let board_id_string = get_cstring(board_id_raw_bytes);\n\n                // We expect that there must be a valid board ID string\n                if !board_id_string.is_empty() {\n                    return Ok(CHKHeader {\n                        board_id: board_id_string.clone(),\n                        header_size: chk_header[\"header_size\"],\n                        kernel_size: chk_header[\"kernel_size\"],\n                        rootfs_size: chk_header[\"rootfs_size\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/common.rs",
    "content": "use log::error;\nuse std::collections::HashMap;\n\n/*\n * Note that all values returned by the parse() function are of type usize; this is a concious decision.\n * Returning usize types makes the calling code much cleaner, but that means that u64 fields won't fit into the return value on 32-bit systems.\n * Thus, only 64-bit systems are supported. This requirement is enforced here.\n */\n#[cfg(not(target_pointer_width = \"64\"))]\ncompile_error!(\"compilation is only allowed for 64-bit targets\");\n\n/// Error return value of structure parsers\n#[derive(Debug, Clone)]\npub struct StructureError;\n\n/// Function to parse basic C-style data structures.\n///\n/// ## Supported Data Types\n///\n/// The following data types are supported:\n/// - u8\n/// - u16\n/// - u24\n/// - u32\n/// - u64\n///\n/// ## Arguments\n///\n/// - `data`: The raw data to apply the structure over\n/// - `structure`: A vector of tuples describing the data structure\n/// - `endianness`: One of: \"big\", \"little\"\n///\n/// ## Example:\n///\n/// ```\n/// # fn main() { #[allow(non_snake_case)] fn _doctest_main_src_structures_common_rs_34_0() -> Result<bool, binwalk::structures::common::StructureError> {\n/// use binwalk::structures;\n///\n/// let my_structure = vec![\n///     (\"magic\", \"u32\"),\n///     (\"size\", \"u64\"),\n///     (\"flags\", \"u8\"),\n///     (\"packed_bytes\", \"u24\"),\n///     (\"checksum\", \"u16\"),\n/// ];\n///\n/// let some_data = b\"AAAA\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08\\x0A\\x0B\\x0C\\x01\\x02\";\n/// let header = structures::common::parse(some_data, &my_structure, \"little\")?;\n///\n/// assert_eq!(header[\"magic\"], 0x41414141);\n/// assert_eq!(header[\"checksum\"], 0x0201);\n/// # Ok(true)\n/// # } _doctest_main_src_structures_common_rs_34_0(); }\n/// ```\npub fn parse(\n    data: &[u8],\n    structure: &Vec<(&str, &str)>,\n    endianness: &str,\n) -> Result<HashMap<String, usize>, StructureError> {\n    const U24_SIZE: usize = 3;\n\n    let mut value: usize;\n    let mut offset: usize = 0;\n    let mut parsed_structure = HashMap::new();\n\n    // Get the size of the defined structure\n    let structure_size = size(structure);\n\n    if let Some(raw_data) = data.get(0..structure_size) {\n        for (name, ctype) in structure {\n            let data_type: String = ctype.to_string();\n\n            match type_to_size(ctype) {\n                None => return Err(StructureError),\n                Some(csize) => {\n                    if csize == std::mem::size_of::<u8>() {\n                        // u8, endianness doesn't matter\n                        value =\n                            u8::from_be_bytes(raw_data[offset..offset + csize].try_into().unwrap())\n                                as usize;\n                    } else if csize == std::mem::size_of::<u16>() {\n                        if endianness == \"big\" {\n                            value = u16::from_be_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        } else {\n                            value = u16::from_le_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        }\n\n                    // Yes Virginia, u24's are real\n                    } else if csize == U24_SIZE {\n                        if endianness == \"big\" {\n                            value = ((raw_data[offset] as usize) << 16)\n                                + ((raw_data[offset + 1] as usize) << 8)\n                                + (raw_data[offset + 2] as usize);\n                        } else {\n                            value = ((raw_data[offset + 2] as usize) << 16)\n                                + ((raw_data[offset + 1] as usize) << 8)\n                                + (raw_data[offset] as usize);\n                        }\n                    } else if csize == std::mem::size_of::<u32>() {\n                        if endianness == \"big\" {\n                            value = u32::from_be_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        } else {\n                            value = u32::from_le_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        }\n                    } else if csize == std::mem::size_of::<u64>() {\n                        if endianness == \"big\" {\n                            value = u64::from_be_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        } else {\n                            value = u64::from_le_bytes(\n                                raw_data[offset..offset + csize].try_into().unwrap(),\n                            ) as usize;\n                        }\n                    } else {\n                        error!(\n                            \"Cannot parse structure element with unknown data type '{data_type}'\"\n                        );\n                        return Err(StructureError);\n                    }\n\n                    offset += csize;\n                    parsed_structure.insert(name.to_string(), value);\n                }\n            }\n        }\n\n        return Ok(parsed_structure);\n    }\n\n    Err(StructureError)\n}\n\n/// Returns the size of a given structure definition.\n///\n/// ## Example:\n///\n/// ```\n/// use binwalk::structures;\n///\n/// let my_structure = vec![\n///     (\"magic\", \"u32\"),\n///     (\"size\", \"u64\"),\n///     (\"flags\", \"u8\"),\n///     (\"checksum\", \"u32\"),\n/// ];\n///\n/// let struct_size = structures::common::size(&my_structure);\n///\n/// assert_eq!(struct_size, 17);\n/// ```\npub fn size(structure: &Vec<(&str, &str)>) -> usize {\n    let mut struct_size: usize = 0;\n\n    for (_name, ctype) in structure {\n        match type_to_size(ctype) {\n            None => continue,\n            Some(member_size) => {\n                struct_size += member_size;\n            }\n        }\n    }\n\n    struct_size\n}\n\n/// Returns the size of a give type string\nfn type_to_size(ctype: &str) -> Option<usize> {\n    // This table must be updated when new data types are added\n    let size_lookup_table: HashMap<&str, usize> =\n        HashMap::from([(\"u8\", 1), (\"u16\", 2), (\"u24\", 3), (\"u32\", 4), (\"u64\", 8)]);\n\n    if !size_lookup_table.contains_key(ctype) {\n        error!(\"Unknown size for structure type '{ctype}'!\");\n        return None;\n    }\n\n    Some(size_lookup_table[ctype])\n}\n"
  },
  {
    "path": "src/structures/cpio.rs",
    "content": "use crate::structures::common::StructureError;\n\n/// Expected minimum size of a CPIO entry header\npub const CPIO_HEADER_SIZE: usize = 110;\n\n/// Storage struct for CPIO entry header info\n#[derive(Debug, Clone, Default)]\npub struct CPIOEntryHeader {\n    pub magic: Vec<u8>,\n    pub data_size: usize,\n    pub file_name: String,\n    pub header_size: usize,\n}\n\n/// Parses a CPIO entry header\npub fn parse_cpio_entry_header(cpio_data: &[u8]) -> Result<CPIOEntryHeader, StructureError> {\n    // Some expected constants\n    const NULL_BYTE_SIZE: usize = 1;\n    const CPIO_MAGIC_START: usize = 0;\n    const CPIO_MAGIC_END: usize = 6;\n    const FILE_SIZE_START: usize = 54;\n    const FILE_SIZE_END: usize = 62;\n    const FILE_NAME_SIZE_START: usize = 94;\n    const FILE_NAME_SIZE_END: usize = 102;\n\n    let available_data: usize = cpio_data.len();\n\n    // TODO: If file mode parsing is added, internal extractor would be pretty easy to implement...\n    if available_data > CPIO_HEADER_SIZE {\n        // Grab the CPIO header magic bytes\n        let header_magic = cpio_data[CPIO_MAGIC_START..CPIO_MAGIC_END].to_vec();\n\n        // Get the ASCII hex string representing the file's data size\n        if let Ok(file_data_size_str) =\n            String::from_utf8(cpio_data[FILE_SIZE_START..FILE_SIZE_END].to_vec())\n        {\n            // Convert the file data size from ASCII hex to an integer\n            if let Ok(file_data_size) = usize::from_str_radix(&file_data_size_str, 16) {\n                // Get the ASCII hex string representing the file name's size\n                if let Ok(file_name_size_str) =\n                    String::from_utf8(cpio_data[FILE_NAME_SIZE_START..FILE_NAME_SIZE_END].to_vec())\n                {\n                    // Convert the file name size from ASCII hex to an integer\n                    if let Ok(file_name_size) = usize::from_str_radix(&file_name_size_str, 16) {\n                        // The file name immediately follows the fixed-length header data.\n                        let file_name_start: usize = CPIO_HEADER_SIZE;\n                        let file_name_end: usize =\n                            file_name_start + file_name_size - NULL_BYTE_SIZE;\n\n                        // Get the file name\n                        if let Some(file_name_raw_bytes) =\n                            cpio_data.get(file_name_start..file_name_end)\n                        {\n                            if let Ok(file_name) = String::from_utf8(file_name_raw_bytes.to_vec()) {\n                                let header_total_size = CPIO_HEADER_SIZE + file_name_size;\n\n                                return Ok(CPIOEntryHeader {\n                                    magic: header_magic.clone(),\n                                    file_name: file_name.clone(),\n                                    data_size: file_data_size + byte_padding(file_data_size),\n                                    header_size: header_total_size\n                                        + byte_padding(header_total_size),\n                                });\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// File data and CPIO headers are padded to 4-byte boundaries\nfn byte_padding(n: usize) -> usize {\n    let modulus: usize = n % 4;\n    if modulus == 0 { 0 } else { 4 - modulus }\n}\n"
  },
  {
    "path": "src/structures/cramfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store info about a CramFS header\n#[derive(Default, Debug, Clone)]\npub struct CramFSHeader {\n    pub size: usize,\n    pub checksum: u32,\n    pub file_count: usize,\n    pub endianness: String,\n}\n\n/// Parses a CramFS header\npub fn parse_cramfs_header(cramfs_data: &[u8]) -> Result<CramFSHeader, StructureError> {\n    // Endian specific magic bytes\n    const BIG_ENDIAN_MAGIC: usize = 0x453DCD28;\n    const LITTLE_ENDIAN_MAGIC: usize = 0x28CD3D45;\n\n    let allowed_magics: Vec<usize> = vec![BIG_ENDIAN_MAGIC, LITTLE_ENDIAN_MAGIC];\n\n    let cramfs_header_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"size\", \"u32\"),\n        (\"flags\", \"u32\"),\n        (\"future\", \"u32\"),\n        (\"signature_p1\", \"u64\"),\n        (\"signature_p2\", \"u64\"),\n        (\"checksum\", \"u32\"),\n        (\"edition\", \"u32\"),\n        (\"block_count\", \"u32\"),\n        (\"file_count\", \"u32\"),\n    ];\n\n    let mut cramfs_info = CramFSHeader {\n        ..Default::default()\n    };\n\n    let cramfs_structure_size = common::size(&cramfs_header_structure);\n\n    // Default to little endian\n    cramfs_info.endianness = \"little\".to_string();\n\n    // Parse the CramFS header, try little endian first\n    if let Ok(mut cramfs_header) = common::parse(\n        cramfs_data,\n        &cramfs_header_structure,\n        &cramfs_info.endianness,\n    ) {\n        // Do the magic bytes match?\n        if allowed_magics.contains(&cramfs_header[\"magic\"]) {\n            // If the magic bytes endianness don't match what's expected for little endian, switch to big endian\n            if cramfs_header[\"magic\"] == BIG_ENDIAN_MAGIC {\n                cramfs_info.endianness = \"big\".to_string();\n\n                // Parse the header again, this time as big endian\n                match common::parse(\n                    cramfs_data,\n                    &cramfs_header_structure,\n                    &cramfs_info.endianness,\n                ) {\n                    Err(_) => {\n                        return Err(StructureError);\n                    }\n                    Ok(cramfs_be_header) => {\n                        cramfs_header = cramfs_be_header.clone();\n                    }\n                }\n            }\n\n            // Reported image size must be larger than the header structure\n            if cramfs_header[\"size\"] > cramfs_structure_size {\n                // Populate info about the CramFS image\n                cramfs_info.size = cramfs_header[\"size\"];\n                cramfs_info.checksum = cramfs_header[\"checksum\"] as u32;\n                cramfs_info.file_count = cramfs_header[\"file_count\"];\n\n                return Ok(cramfs_info);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/csman.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store CSMAN header info\n#[derive(Debug, Default, Clone)]\npub struct CSManHeader {\n    pub compressed: bool,\n    pub data_size: usize,\n    pub endianness: String,\n    pub header_size: usize,\n}\n\n/// Parses a CSMAN header\npub fn parse_csman_header(csman_data: &[u8]) -> Result<CSManHeader, StructureError> {\n    const COMPRESSED_MAGIC: &[u8] = b\"\\x78\";\n    const LITTLE_ENDIAN_MAGIC: usize = 0x4353;\n\n    let csman_header_structure = vec![\n        (\"magic\", \"u16\"),\n        (\"unknown1\", \"u16\"),\n        (\"compressed_size\", \"u32\"),\n        (\"unknown2\", \"u32\"),\n        (\"decompressed_size\", \"u32\"),\n    ];\n\n    let mut result = CSManHeader {\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(mut csman_header) = common::parse(csman_data, &csman_header_structure, \"big\") {\n        // Detect the endianness\n        if csman_header[\"magic\"] == LITTLE_ENDIAN_MAGIC {\n            // If this is a little endian header, re-parse the data as little endian\n            if let Ok(csman_header_le) =\n                common::parse(csman_data, &csman_header_structure, \"little\")\n            {\n                csman_header = csman_header_le.clone();\n                result.endianness = \"little\".to_string();\n            }\n        } else {\n            result.endianness = \"big\".to_string();\n        }\n\n        // Should have been able to determine the endianness\n        if !result.endianness.is_empty() {\n            result.data_size = csman_header[\"compressed_size\"];\n            result.header_size = common::size(&csman_header_structure);\n            result.compressed =\n                csman_header[\"compressed_size\"] != csman_header[\"decompressed_size\"];\n\n            // If compressed, check the expected compressed magic bytes\n            if result.compressed {\n                if let Some(compressed_magic) =\n                    csman_data.get(result.header_size..result.header_size + COMPRESSED_MAGIC.len())\n                {\n                    if compressed_magic != COMPRESSED_MAGIC {\n                        return Err(StructureError);\n                    }\n                }\n            }\n\n            return Ok(result);\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a single CSMan DAT file entry\n#[derive(Debug, Default, Clone)]\npub struct CSManEntry {\n    pub size: usize,\n    pub eof: bool,\n    pub key: usize,\n    pub value: Vec<u8>,\n}\n\n/// Parses a single CSMan DAT file entry\npub fn parse_csman_entry(\n    entry_data: &[u8],\n    endianness: &str,\n) -> Result<CSManEntry, StructureError> {\n    const EOF_TAG: usize = 0;\n\n    // The last entry is just a single 4-byte NULL value\n    let csman_last_entry_structure = vec![(\"eof\", \"u32\")];\n\n    // Entries consist of a 4-byte identifier, a 2-byte size, and a value\n    let csman_entry_structure = vec![\n        (\"key\", \"u32\"),\n        (\"size\", \"u16\"),\n        // value of size bytes immediately follows\n    ];\n\n    let mut entry = CSManEntry {\n        ..Default::default()\n    };\n\n    if let Ok(entry_header) = common::parse(entry_data, &csman_entry_structure, endianness) {\n        let value_start: usize = common::size(&csman_entry_structure);\n        let value_end: usize = value_start + entry_header[\"size\"];\n\n        if let Some(entry_value) = entry_data.get(value_start..value_end) {\n            entry.key = entry_header[\"key\"];\n            entry.value = entry_value.to_vec();\n            entry.size = common::size(&csman_entry_structure) + entry_value.len();\n            return Ok(entry);\n        }\n    } else if let Ok(entry_header) =\n        common::parse(entry_data, &csman_last_entry_structure, endianness)\n    {\n        if entry_header[\"eof\"] == EOF_TAG {\n            entry.eof = true;\n            entry.size = common::size(&csman_last_entry_structure);\n            return Ok(entry);\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/deb.rs",
    "content": "use crate::structures::common::StructureError;\n\n/// Struct to store DEB file info\n#[derive(Debug, Clone, Default)]\npub struct DebHeader {\n    pub file_size: usize,\n}\n\n/// Parse a DEB file\npub fn parse_deb_header(deb_data: &[u8]) -> Result<DebHeader, StructureError> {\n    const END_MARKER_SIZE: usize = 2;\n    const DATA_FILE_SIZE_LEN: usize = 10;\n    const DATA_FILE_SIZE_OFFSET: usize = 48;\n    const CONTROL_FILE_SIZE_END: usize = 130;\n    const CONTROL_FILE_SIZE_START: usize = 120;\n\n    let mut deb_header = DebHeader {\n        ..Default::default()\n    };\n\n    // Index into the header to get the raw bytes of the decimal ASCII string that contains the control file size\n    if let Some(control_file_size_data) =\n        deb_data.get(CONTROL_FILE_SIZE_START..CONTROL_FILE_SIZE_END)\n    {\n        // Convert the raw bytes into an ASCII string\n        if let Ok(control_file_size_str) = String::from_utf8(control_file_size_data.to_vec()) {\n            // Trim white space from the string and convert to an integer value\n            if let Ok(control_file_size) = control_file_size_str.trim().parse::<usize>() {\n                // Calculate the offsets to the decimal ASCII string that contains the data file size\n                let data_file_size_start: usize = CONTROL_FILE_SIZE_END\n                    + END_MARKER_SIZE\n                    + control_file_size\n                    + DATA_FILE_SIZE_OFFSET;\n                let data_file_size_end: usize = data_file_size_start + DATA_FILE_SIZE_LEN;\n\n                // Index into the header to get the raw bytes of the deciaml ASCII string that contains the data file size\n                if let Some(data_file_size_data) =\n                    deb_data.get(data_file_size_start..data_file_size_end)\n                {\n                    // Convert the raw bytes to an ASCII string\n                    if let Ok(data_file_size_str) = String::from_utf8(data_file_size_data.to_vec())\n                    {\n                        // Trim whitespace from the string and convert to an integer value\n                        if let Ok(data_file_size) = data_file_size_str.trim().parse::<usize>() {\n                            // Total file size is the end of the file data size ASCII field, plus the 2-byte end marker, plus the length of the following data file\n                            // TODO: This size seems to be short by 2 bytes? Not a big deal for our purposes, but still...\n                            deb_header.file_size =\n                                data_file_size_end + END_MARKER_SIZE + data_file_size;\n                            return Ok(deb_header);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dkbs.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store DKBS header info\n#[derive(Debug, Default, Clone)]\npub struct DKBSHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n    pub board_id: String,\n    pub version: String,\n    pub boot_device: String,\n    pub endianness: String,\n}\n\n/// Parses a DKBS header\npub fn parse_dkbs_header(dkbs_data: &[u8]) -> Result<DKBSHeader, StructureError> {\n    // Header is a fixed size\n    const HEADER_SIZE: usize = 0xA0;\n\n    // Constant offsets for strings and known header fields\n    const BOARD_ID_START: usize = 0;\n    const BOARD_ID_END: usize = 0x20;\n    const VERSION_START: usize = 0x28;\n    const VERSION_END: usize = 0x48;\n    const BOOT_DEVICE_START: usize = 0x70;\n    const BOOT_DEVICE_END: usize = 0x90;\n    const DATA_SIZE_START: usize = 0x68;\n    const DATA_SIZE_END: usize = DATA_SIZE_START + 4;\n\n    let data_size_field = vec![(\"size\", \"u32\")];\n\n    let mut header = DKBSHeader {\n        header_size: HEADER_SIZE,\n        ..Default::default()\n    };\n\n    // Available data should be at least big enough for the header to fit\n    if dkbs_data.len() >= HEADER_SIZE {\n        // Parse the version, board ID, and boot device strings\n        header.version = get_cstring(&dkbs_data[VERSION_START..VERSION_END]);\n        header.board_id = get_cstring(&dkbs_data[BOARD_ID_START..BOARD_ID_END]);\n        header.boot_device = get_cstring(&dkbs_data[BOOT_DEVICE_START..BOOT_DEVICE_END]);\n\n        // Sanity check to make sure the strings were retrieved\n        if !header.version.is_empty()\n            && !header.board_id.is_empty()\n            && !header.boot_device.is_empty()\n        {\n            if let Some(data_size_bytes) = dkbs_data.get(DATA_SIZE_START..DATA_SIZE_END) {\n                // Parse the payload size field\n                if let Ok(data_size) = common::parse(data_size_bytes, &data_size_field, \"big\") {\n                    if data_size[\"size\"] & 0xFF000000 == 0 {\n                        header.data_size = data_size[\"size\"];\n                        header.endianness = \"big\".to_string();\n                    } else if let Ok(data_size) =\n                        common::parse(data_size_bytes, &data_size_field, \"little\")\n                    {\n                        header.data_size = data_size[\"size\"];\n                        header.endianness = \"little\".to_string();\n                    }\n                }\n\n                if header.data_size != 0 {\n                    return Ok(header);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dlink_tlv.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store DLink TLV firmware header info\n#[derive(Debug, Default, Clone)]\npub struct DlinkTLVHeader {\n    pub model_name: String,\n    pub board_id: String,\n    pub header_size: usize,\n    pub data_size: usize,\n    pub data_checksum: String,\n}\n\n/// Parses a DLink TLV firmware header\npub fn parse_dlink_tlv_header(tlv_data: &[u8]) -> Result<DlinkTLVHeader, StructureError> {\n    const MAX_STRING_LENGTH: usize = 0x20;\n\n    const MODEL_NAME_OFFSET: usize = 4;\n    const BOARD_ID_OFFSET: usize = 0x24;\n    const MD5_HASH_OFFSET: usize = 0x4C;\n    const DATA_TLV_OFFSET: usize = 0x6C;\n\n    const HEADER_SIZE: usize = 0x74;\n    const EXPECTED_DATA_TYPE: usize = 1;\n\n    let tlv_structure = vec![\n        (\"type\", \"u32\"),\n        (\"length\", \"u32\"),\n        // value immediately follows\n    ];\n\n    let mut header = DlinkTLVHeader {\n        ..Default::default()\n    };\n\n    // Get the header data\n    if let Some(header_data) = tlv_data.get(0..HEADER_SIZE) {\n        // Get the strings from the header\n        header.board_id =\n            get_cstring(&header_data[BOARD_ID_OFFSET..BOARD_ID_OFFSET + MAX_STRING_LENGTH]);\n        header.model_name =\n            get_cstring(&header_data[MODEL_NAME_OFFSET..MODEL_NAME_OFFSET + MAX_STRING_LENGTH]);\n        header.data_checksum =\n            get_cstring(&header_data[MD5_HASH_OFFSET..MD5_HASH_OFFSET + MAX_STRING_LENGTH]);\n\n        // Make sure we got the expected strings OK (checksum is not always included)\n        if !header.model_name.is_empty() && !header.board_id.is_empty() {\n            // Parse the type and length values that describe the data the follows the header\n            if let Ok(data_tlv) =\n                common::parse(&header_data[DATA_TLV_OFFSET..], &tlv_structure, \"little\")\n            {\n                // Sanity check the reported type (should be 1)\n                if data_tlv[\"type\"] == EXPECTED_DATA_TYPE {\n                    header.data_size = data_tlv[\"length\"];\n                    header.header_size = HEADER_SIZE;\n                    return Ok(header);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dlob.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store DLOB header info\n#[derive(Debug, Default, Clone)]\npub struct DlobHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parses a DLOB header\npub fn parse_dlob_header(dlob_data: &[u8]) -> Result<DlobHeader, StructureError> {\n    let dlob_structure_p1 = vec![\n        (\"magic\", \"u32\"),\n        (\"metadata_size\", \"u32\"),\n        (\"data_size\", \"u32\"),\n    ];\n\n    let dlob_structure_p2 = vec![\n        (\"magic\", \"u32\"),\n        (\"metadata_size\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"unknown\", \"u64\"),\n        (\"unknown\", \"u64\"),\n    ];\n\n    // Parse the first half of the header\n    if let Ok(dlob_header_p1) = common::parse(dlob_data, &dlob_structure_p1, \"big\") {\n        // Calculate the offset to the second part of the header\n        let dlob_header_p2_offset: usize =\n            common::size(&dlob_structure_p1) + dlob_header_p1[\"metadata_size\"];\n\n        // It is expected that the first header is metadata only\n        if dlob_header_p1[\"data_size\"] == 0 {\n            // Parse the second part of the header\n            if let Some(header_p2_data) = dlob_data.get(dlob_header_p2_offset..) {\n                if let Ok(dlob_header_p2) = common::parse(header_p2_data, &dlob_structure_p2, \"big\")\n                {\n                    // Both parts should have the same magic bytes\n                    if dlob_header_p1[\"magic\"] == dlob_header_p2[\"magic\"] {\n                        // Calculate total header size\n                        let header_total_size: usize = dlob_header_p2_offset\n                            + common::size(&dlob_structure_p2)\n                            + dlob_header_p2[\"metadata_size\"];\n\n                        // Basic sanity check on the reported data size vs header size\n                        if header_total_size < dlob_header_p2[\"data_size\"] {\n                            return Ok(DlobHeader {\n                                header_size: header_total_size,\n                                data_size: dlob_header_p2[\"data_size\"],\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dmg.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store DMG footer info\n#[derive(Debug, Default, Clone)]\npub struct DMGFooter {\n    pub footer_size: usize,\n    pub data_length: usize,\n    pub xml_length: usize,\n}\n\n/// Parses a DMG footer structure\npub fn parse_dmg_footer(dmg_data: &[u8]) -> Result<DMGFooter, StructureError> {\n    // https://newosxbook.com/DMG.html\n    let dmg_footer_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"version\", \"u32\"),\n        (\"header_size\", \"u32\"),\n        (\"flags\", \"u32\"),\n        (\"running_data_fork_offset\", \"u64\"),\n        (\"data_fork_offset\", \"u64\"),\n        (\"data_fork_length\", \"u64\"),\n        (\"rsrc_fork_offset\", \"u64\"),\n        (\"rsrc_fork_length\", \"u64\"),\n        (\"segment_number\", \"u32\"),\n        (\"segment_count\", \"u32\"),\n        (\"segment_id_p1\", \"u64\"),\n        (\"segment_id_p2\", \"u64\"),\n        (\"data_checksum_type\", \"u32\"),\n        (\"data_checksum_size\", \"u32\"),\n        (\"data_checksum_1\", \"u32\"),\n        (\"data_checksum_2\", \"u32\"),\n        (\"data_checksum_3\", \"u32\"),\n        (\"data_checksum_4\", \"u32\"),\n        (\"data_checksum_5\", \"u32\"),\n        (\"data_checksum_6\", \"u32\"),\n        (\"data_checksum_7\", \"u32\"),\n        (\"data_checksum_8\", \"u32\"),\n        (\"data_checksum_9\", \"u32\"),\n        (\"data_checksum_10\", \"u32\"),\n        (\"data_checksum_11\", \"u32\"),\n        (\"data_checksum_12\", \"u32\"),\n        (\"data_checksum_13\", \"u32\"),\n        (\"data_checksum_14\", \"u32\"),\n        (\"data_checksum_15\", \"u32\"),\n        (\"data_checksum_16\", \"u32\"),\n        (\"data_checksum_17\", \"u32\"),\n        (\"data_checksum_18\", \"u32\"),\n        (\"data_checksum_19\", \"u32\"),\n        (\"data_checksum_20\", \"u32\"),\n        (\"data_checksum_21\", \"u32\"),\n        (\"data_checksum_22\", \"u32\"),\n        (\"data_checksum_23\", \"u32\"),\n        (\"data_checksum_24\", \"u32\"),\n        (\"data_checksum_25\", \"u32\"),\n        (\"data_checksum_26\", \"u32\"),\n        (\"data_checksum_27\", \"u32\"),\n        (\"data_checksum_28\", \"u32\"),\n        (\"data_checksum_29\", \"u32\"),\n        (\"data_checksum_30\", \"u32\"),\n        (\"data_checksum_31\", \"u32\"),\n        (\"data_checksum_32\", \"u32\"),\n        (\"xml_offset\", \"u64\"),\n        (\"xml_length\", \"u64\"),\n        (\"reserved_1\", \"u64\"),\n        (\"reserved_2\", \"u64\"),\n        (\"reserved_3\", \"u64\"),\n        (\"reserved_4\", \"u64\"),\n        (\"reserved_5\", \"u64\"),\n        (\"reserved_6\", \"u64\"),\n        (\"reserved_7\", \"u64\"),\n        (\"reserved_8\", \"u64\"),\n        (\"reserved_9\", \"u64\"),\n        (\"reserved_10\", \"u64\"),\n        (\"reserved_11\", \"u64\"),\n        (\"reserved_12\", \"u64\"),\n        (\"reserved_13\", \"u64\"),\n        (\"reserved_14\", \"u64\"),\n        (\"reserved_15\", \"u64\"),\n        (\"checksum_type\", \"u32\"),\n        (\"checksum_size\", \"u32\"),\n        (\"checksum_1\", \"u32\"),\n        (\"checksum_2\", \"u32\"),\n        (\"checksum_3\", \"u32\"),\n        (\"checksum_4\", \"u32\"),\n        (\"checksum_5\", \"u32\"),\n        (\"checksum_6\", \"u32\"),\n        (\"checksum_7\", \"u32\"),\n        (\"checksum_8\", \"u32\"),\n        (\"checksum_9\", \"u32\"),\n        (\"checksum_10\", \"u32\"),\n        (\"checksum_11\", \"u32\"),\n        (\"checksum_12\", \"u32\"),\n        (\"checksum_13\", \"u32\"),\n        (\"checksum_14\", \"u32\"),\n        (\"checksum_15\", \"u32\"),\n        (\"checksum_16\", \"u32\"),\n        (\"checksum_17\", \"u32\"),\n        (\"checksum_18\", \"u32\"),\n        (\"checksum_19\", \"u32\"),\n        (\"checksum_20\", \"u32\"),\n        (\"checksum_21\", \"u32\"),\n        (\"checksum_22\", \"u32\"),\n        (\"checksum_23\", \"u32\"),\n        (\"checksum_24\", \"u32\"),\n        (\"checksum_25\", \"u32\"),\n        (\"checksum_26\", \"u32\"),\n        (\"checksum_27\", \"u32\"),\n        (\"checksum_28\", \"u32\"),\n        (\"checksum_29\", \"u32\"),\n        (\"checksum_30\", \"u32\"),\n        (\"checksum_31\", \"u32\"),\n        (\"checksum_32\", \"u32\"),\n        (\"image_variant\", \"u32\"),\n        (\"sector_count\", \"u64\"),\n        (\"reserved_16\", \"u32\"),\n        (\"reserved_17\", \"u32\"),\n        (\"reserved_18\", \"u32\"),\n    ];\n\n    let structure_size: usize = common::size(&dmg_footer_structure);\n\n    // Parse the DMG footer\n    if let Ok(dmg_footer) = common::parse(dmg_data, &dmg_footer_structure, \"big\") {\n        // Sanity check, make sure the reported header size is the size of this structure\n        if dmg_footer[\"header_size\"] == structure_size {\n            return Ok(DMGFooter {\n                data_length: dmg_footer[\"data_fork_length\"],\n                xml_length: dmg_footer[\"xml_length\"],\n                footer_size: structure_size,\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dms.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store DMS header info\n#[derive(Debug, Default, Clone)]\npub struct DMSHeader {\n    pub image_size: usize,\n}\n\n/// Parses a DMS header\npub fn parse_dms_header(dms_data: &[u8]) -> Result<DMSHeader, StructureError> {\n    const MAGIC_P1: usize = 0x4D47;\n    const MAGIC_P2: usize = 0x3C31303E;\n\n    let dms_structure = vec![\n        (\"unknown1\", \"u16\"),\n        (\"magic_p1\", \"u16\"),\n        (\"magic_p2\", \"u32\"),\n        (\"unknown2\", \"u32\"),\n        (\"image_size\", \"u32\"),\n    ];\n\n    // Parse the first half of the header\n    if let Ok(dms_header) = common::parse(dms_data, &dms_structure, \"big\") {\n        if dms_header[\"magic_p1\"] == MAGIC_P1 && dms_header[\"magic_p2\"] == MAGIC_P2 {\n            return Ok(DMSHeader {\n                image_size: dms_header[\"image_size\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/dpapi.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/*\n Blob structure: from mimikatz repository.\n   DWORD\tdwVersion;\n   GUID\tguidProvider;\n   DWORD\tdwMasterKeyVersion;\n   GUID\tguidMasterKey;\n   DWORD\tdwFlags;\n\n   DWORD\tdwDescriptionLen;\n   PWSTR\tszDescription;\n\n   ALG_ID\talgCrypt;\n   DWORD\tdwAlgCryptLen;\n\n   DWORD\tdwSaltLen;\n   PBYTE\tpbSalt;\n\n   DWORD\tdwHmacKeyLen;\n   PBYTE\tpbHmackKey;\n\n   ALG_ID\talgHash;\n   DWORD\tdwAlgHashLen;\n\n   DWORD\tdwHmac2KeyLen;\n   PBYTE\tpbHmack2Key;\n\n   DWORD\tdwDataLen;\n   PBYTE\tpbData;\n\n   DWORD\tdwSignLen;\n   PBYTE\tpbSign;\n*/\n\n/// Struct to store DPAPI blob structure\n#[derive(Debug, Default, Clone)]\npub struct DPAPIBlobHeader {\n    pub header_size: usize,\n    pub blob_size: usize,\n    pub version: usize,\n    pub provider_id: usize,\n    pub master_key_version: usize,\n    pub master_key_id: usize,\n    pub flags: usize,\n    pub description_len: usize,\n    pub crypto_algorithm: usize,\n    pub crypti_alg_len: usize,\n    pub salt_len: usize,\n    pub hmac_key_len: usize,\n    pub hash_algorithm: usize,\n    pub hash_alg_len: usize,\n    pub hmac2_key_len: usize,\n    pub data_len: usize,\n    pub sign_len: usize,\n}\n\n/// Parse a DPAPI BLOB\npub fn parse_dpapi_blob_header(dpapi_blob_data: &[u8]) -> Result<DPAPIBlobHeader, StructureError> {\n    let initial_dpapi_structure = vec![\n        (\"version\", \"u32\"),\n        (\"provider_id\", \"u128\"),\n        (\"master_key_version\", \"u32\"),\n        (\"master_key_id\", \"u128\"),\n        (\"flags\", \"u32\"),\n        (\"description_len\", \"u32\"),\n    ];\n    let mut offset: usize = (32 + 128 + 32 + 128 + 32 + 32) / 8;\n\n    let mut dpapi_header = common::parse(dpapi_blob_data, &initial_dpapi_structure, \"little\")?;\n    let description_len = dpapi_header[\"description_len\"];\n\n    if description_len % 2 != 0 {\n        return Err(StructureError);\n    }\n\n    let utf16_vec =\n        utf8_to_utf16(&dpapi_blob_data[offset..=offset + description_len]).ok_or(StructureError)?;\n    let desc = String::from_utf16(&utf16_vec).map_err(|_| StructureError)?;\n\n    // NULL character becomes size 1 from size 2\n    if description_len != desc.len() - 1 {\n        return Err(StructureError);\n    }\n\n    offset += description_len;\n\n    let next_dpapi_structure = vec![\n        (\"crypto_algorithm\", \"u32\"),\n        (\"crypti_alg_len\", \"u32\"),\n        (\"salt_len\", \"u32\"),\n    ];\n    dpapi_header.extend(common::parse(\n        &dpapi_blob_data[offset..],\n        &next_dpapi_structure,\n        \"little\",\n    )?);\n    let salt_len = dpapi_header[\"salt_len\"];\n    offset += (32 + 32 + 32) / 8 + salt_len;\n\n    let next_dpapi_structure = vec![(\"hmac_key_len\", \"u32\")];\n    dpapi_header.extend(common::parse(\n        &dpapi_blob_data[offset..],\n        &next_dpapi_structure,\n        \"little\",\n    )?);\n    let hmac_key_len = dpapi_header[\"hmac_key_len\"];\n    offset += 32 / 8 + hmac_key_len;\n\n    let next_dpapi_structure = vec![\n        (\"hash_algorithm\", \"u32\"),\n        (\"hash_alg_len\", \"u32\"),\n        (\"hmac2_key_len\", \"u32\"),\n    ];\n    dpapi_header.extend(common::parse(\n        &dpapi_blob_data[offset..],\n        &next_dpapi_structure,\n        \"little\",\n    )?);\n    let hmac2_key_len = dpapi_header[\"hmac2_key_len\"];\n    offset += (32 + 32 + 32) / 8 + hmac2_key_len;\n\n    let next_dpapi_structure = vec![(\"data_len\", \"u32\")];\n    dpapi_header.extend(common::parse(\n        &dpapi_blob_data[offset..],\n        &next_dpapi_structure,\n        \"little\",\n    )?);\n    let data_len = dpapi_header[\"data_len\"];\n    offset += 32 / 8 + data_len;\n\n    let next_dpapi_structure = vec![(\"sign_len\", \"u32\")];\n    dpapi_header.extend(common::parse(\n        &dpapi_blob_data[offset..],\n        &next_dpapi_structure,\n        \"little\",\n    )?);\n    let sign_len = dpapi_header[\"sign_len\"];\n    offset += 32 / 8 + sign_len;\n\n    Ok(DPAPIBlobHeader {\n        header_size: (32 * 13 + 128 * 2) / 8,\n        blob_size: offset,\n        version: dpapi_header[\"version\"],\n        provider_id: dpapi_header[\"provider_id\"],\n        master_key_version: dpapi_header[\"master_key_version\"],\n        master_key_id: dpapi_header[\"master_key_id\"],\n        flags: dpapi_header[\"flags\"],\n        description_len,\n        crypto_algorithm: dpapi_header[\"crypto_algorithm\"],\n        crypti_alg_len: dpapi_header[\"crypti_alg_len\"],\n        salt_len,\n        hmac_key_len,\n        hash_algorithm: dpapi_header[\"hash_algorithm\"],\n        hash_alg_len: dpapi_header[\"hash_alg_len\"],\n        hmac2_key_len,\n        data_len,\n        sign_len,\n    })\n}\n\n/// Convert &[u8] into &[u16] as vec\nfn utf8_to_utf16(byte_array: &[u8]) -> Option<Vec<u16>> {\n    let mut utf16_vec = Vec::with_capacity(byte_array.len() / 2);\n    for i in 0..utf16_vec.len() {\n        let buff = byte_array[2 * i..=2 * i + 1].try_into().ok()?;\n        utf16_vec[i] = u16::from_be_bytes(buff); // Big endian as to keep bit order\n    }\n    Some(utf16_vec)\n}\n"
  },
  {
    "path": "src/structures/dtb.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store DTB info\n#[derive(Debug, Default, Clone)]\npub struct DTBHeader {\n    pub total_size: usize,\n    pub version: usize,\n    pub cpu_id: usize,\n    pub struct_offset: usize,\n    pub strings_offset: usize,\n    pub struct_size: usize,\n    pub strings_size: usize,\n}\n\n/// Parse  DTB header\npub fn parse_dtb_header(dtb_data: &[u8]) -> Result<DTBHeader, StructureError> {\n    // Expected version numbers\n    const EXPECTED_VERSION: usize = 17;\n    const EXPECTED_COMPAT_VERSION: usize = 16;\n\n    const STRUCT_ALIGNMENT: usize = 4;\n    const MEM_RESERVATION_ALIGNMENT: usize = 8;\n\n    let dtb_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"total_size\", \"u32\"),\n        (\"dt_struct_offset\", \"u32\"),\n        (\"dt_strings_offset\", \"u32\"),\n        (\"mem_reservation_block_offset\", \"u32\"),\n        (\"version\", \"u32\"),\n        (\"min_compatible_version\", \"u32\"),\n        (\"cpu_id\", \"u32\"),\n        (\"dt_strings_size\", \"u32\"),\n        (\"dt_struct_size\", \"u32\"),\n    ];\n\n    let dtb_structure_size = common::size(&dtb_structure);\n\n    // Parse the header\n    if let Ok(dtb_header) = common::parse(dtb_data, &dtb_structure, \"big\") {\n        // Check the reported versioning\n        if dtb_header[\"version\"] == EXPECTED_VERSION\n            && dtb_header[\"min_compatible_version\"] == EXPECTED_COMPAT_VERSION\n        {\n            // Check required byte alignments for the specified offsets\n            if (dtb_header[\"dt_struct_offset\"] & STRUCT_ALIGNMENT) == 0\n                && (dtb_header[\"mem_reservation_block_offset\"] % MEM_RESERVATION_ALIGNMENT) == 0\n            {\n                // All offsets must start after the header structure\n                if dtb_header[\"dt_struct_offset\"] >= dtb_structure_size\n                    && dtb_header[\"dt_strings_offset\"] >= dtb_structure_size\n                    && dtb_header[\"mem_reservation_block_offset\"] >= dtb_structure_size\n                {\n                    return Ok(DTBHeader {\n                        total_size: dtb_header[\"total_size\"],\n                        version: dtb_header[\"version\"],\n                        cpu_id: dtb_header[\"cpu_id\"],\n                        struct_offset: dtb_header[\"dt_struct_offset\"],\n                        strings_offset: dtb_header[\"dt_strings_offset\"],\n                        struct_size: dtb_header[\"dt_struct_size\"],\n                        strings_size: dtb_header[\"dt_strings_size\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Describes a DTB node entry\n#[derive(Debug, Default, Clone)]\npub struct DTBNode {\n    pub begin: bool,\n    pub end: bool,\n    pub eof: bool,\n    pub nop: bool,\n    pub property: bool,\n    pub name: String,\n    pub data: Vec<u8>,\n    pub total_size: usize,\n}\n\n/// Parse a DTB node from the DTB data structure\npub fn parse_dtb_node(dtb_header: &DTBHeader, dtb_data: &[u8], node_offset: usize) -> DTBNode {\n    const FDT_BEGIN_NODE: usize = 1;\n    const FDT_END_NODE: usize = 2;\n    const FDT_PROP: usize = 3;\n    const FDT_NOP: usize = 4;\n    const FDT_END: usize = 9;\n\n    let node_token = vec![(\"id\", \"u32\")];\n    let node_property = vec![(\"data_len\", \"u32\"), (\"name_offset\", \"u32\")];\n\n    let mut node = DTBNode {\n        ..Default::default()\n    };\n\n    if let Some(node_data) = dtb_data.get(node_offset..) {\n        if let Ok(token) = common::parse(node_data, &node_token, \"big\") {\n            // Set total node size to the size of the token entry\n            node.total_size = common::size(&node_token);\n\n            if token[\"id\"] == FDT_END_NODE {\n                node.end = true;\n            } else if token[\"id\"] == FDT_NOP {\n                node.nop = true;\n            } else if token[\"id\"] == FDT_END {\n                node.eof = true;\n            // All other node types must include additional data, so the available data must be greater than just the token entry size\n            } else if node_data.len() > node.total_size {\n                if token[\"id\"] == FDT_BEGIN_NODE {\n                    // Begin nodes are immediately followed by a NULL-terminated name, padded to a 4-byte boundary if necessary\n                    node.begin = true;\n                    node.name = get_cstring(&node_data[node.total_size..]);\n                    node.total_size += dtb_aligned(node.name.len() + 1);\n                } else if token[\"id\"] == FDT_PROP {\n                    // Property tokens are followed by a property structure\n                    if let Ok(property) =\n                        common::parse(&node_data[node.total_size..], &node_property, \"big\")\n                    {\n                        // Update the total node size to include the property structure\n                        node.total_size += common::size(&node_property);\n\n                        // The property's data will immediately follow the property structure; property data may be NULL-padded for alignment\n                        if let Some(property_data) =\n                            node_data.get(node.total_size..node.total_size + property[\"data_len\"])\n                        {\n                            node.data = property_data.to_vec();\n                            node.total_size += dtb_aligned(node.data.len());\n\n                            // Get the property name from the DTB strings table\n                            if let Some(property_name_data) =\n                                dtb_data.get(dtb_header.strings_offset + property[\"name_offset\"]..)\n                            {\n                                node.name = get_cstring(property_name_data);\n                                if !node.name.is_empty() {\n                                    node.property = true;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    node\n}\n\n/// DTB entries must be aligned to 4-byte boundaries\nfn dtb_aligned(len: usize) -> usize {\n    const ALIGNMENT: usize = 4;\n\n    let rem = len % ALIGNMENT;\n\n    if rem == 0 {\n        len\n    } else {\n        len + (ALIGNMENT - rem)\n    }\n}\n"
  },
  {
    "path": "src/structures/dxbc.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n#[derive(Debug, Default, Clone)]\npub struct DXBCHeader {\n    pub size: usize,\n    pub chunk_ids: Vec<[u8; 4]>,\n}\n\n// http://timjones.io/blog/archive/2015/09/02/parsing-direct3d-shader-bytecode\npub fn parse_dxbc_header(data: &[u8]) -> Result<DXBCHeader, StructureError> {\n    let dxbc_header_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"signature_p1\", \"u64\"),\n        (\"signature_p2\", \"u64\"),\n        (\"one\", \"u32\"),\n        (\"total_size\", \"u32\"),\n        (\"chunk_count\", \"u32\"),\n    ];\n\n    // Parse the header\n    if let Ok(header) = common::parse(data, &dxbc_header_structure, \"little\") {\n        if header[\"one\"] != 1 {\n            return Err(StructureError);\n        }\n\n        // Sanity check: There are at least 14 known chunks, but most likely no more than 32.\n        // Prevents the for loop from spiraling into an OOM on the offchance that both the magic and \"one\" check pass on garbage data\n        if header[\"chunk_count\"] > 32 {\n            return Err(StructureError);\n        }\n\n        let header_end = common::size(&dxbc_header_structure);\n\n        let mut chunk_ids = vec![];\n        for i in 0..header[\"chunk_count\"] {\n            let offset_data = data\n                .get((header_end + i * 4)..(header_end + i * 4) + 4)\n                .ok_or(StructureError)?;\n            let offset = u32::from_le_bytes(offset_data.try_into().unwrap()) as usize;\n\n            chunk_ids.push(\n                data.get(offset..offset + 4)\n                    .ok_or(StructureError)?\n                    .try_into()\n                    .unwrap(),\n            );\n        }\n\n        return Ok(DXBCHeader {\n            size: header[\"total_size\"],\n            chunk_ids,\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/efigpt.rs",
    "content": "use crate::common::{crc32, is_offset_safe};\nuse crate::structures::common::{self, StructureError};\n\nconst BLOCK_SIZE: usize = 512;\n\n/// Struct to store EFI GPT header info\n#[derive(Debug, Default, Clone)]\npub struct EFIGPTHeader {\n    pub total_size: usize,\n}\n\n/// Parses an EFI GPT header\npub fn parse_efigpt_header(efi_data: &[u8]) -> Result<EFIGPTHeader, StructureError> {\n    const EXPTECTED_REVISION: usize = 0x00010000;\n\n    // https://uefi.org/sites/default/files/resources/UEFI_Spec_2_10_Aug29.pdf, p.116\n    let efi_gpt_structure = vec![\n        (\"magic\", \"u64\"),\n        (\"revision\", \"u32\"),\n        (\"header_size\", \"u32\"),\n        (\"header_crc\", \"u32\"),\n        (\"reserved\", \"u32\"),\n        (\"my_lba\", \"u64\"),\n        (\"alternate_lba\", \"u64\"),\n        (\"first_usable_lba\", \"u64\"),\n        (\"last_usable_lba\", \"u64\"),\n        (\"disk_guid_p1\", \"u64\"),\n        (\"disk_guid_p2\", \"u64\"),\n        (\"partition_entry_lba\", \"u64\"),\n        (\"partition_entry_count\", \"u32\"),\n        (\"partition_entry_size\", \"u32\"),\n        (\"partition_entries_crc\", \"u32\"),\n    ];\n\n    let mut result = EFIGPTHeader {\n        ..Default::default()\n    };\n\n    // EFI GPT structure starts at the second block (first block is MBR)\n    if let Some(gpt_data) = efi_data.get(BLOCK_SIZE..) {\n        // Parse the EFI GPT structure\n        if let Ok(gpt_header) = common::parse(gpt_data, &efi_gpt_structure, \"little\") {\n            // Make sure the reserved field is NULL\n            if gpt_header[\"reserved\"] == 0 {\n                // Make sure the revision field is the expected valid\n                if gpt_header[\"revision\"] == EXPTECTED_REVISION {\n                    // Calculate the start and end offsets of the partition entries\n                    let partition_entries_start: usize =\n                        lba_to_offset(gpt_header[\"partition_entry_lba\"]);\n                    let partition_entries_end: usize = partition_entries_start\n                        + (gpt_header[\"partition_entry_count\"]\n                            * gpt_header[\"partition_entry_size\"]);\n\n                    // Get the partition entires\n                    if let Some(partition_entries_data) =\n                        efi_data.get(partition_entries_start..partition_entries_end)\n                    {\n                        // Validate the partition entries' CRC\n                        if crc32(partition_entries_data)\n                            == (gpt_header[\"partition_entries_crc\"] as u32)\n                        {\n                            let mut next_partition_offset = 0;\n                            let mut previous_partition_offset = None;\n                            let available_data = partition_entries_data.len();\n\n                            // Loop through all partition entries\n                            while is_offset_safe(\n                                available_data,\n                                next_partition_offset,\n                                previous_partition_offset,\n                            ) {\n                                if let Some(partition) = parse_gpt_partition_entry(\n                                    &partition_entries_data[next_partition_offset..],\n                                ) {\n                                    // EOF is the end of the farthest away partition\n                                    if partition.start_offset < partition.end_offset\n                                        && partition.end_offset > result.total_size\n                                    {\n                                        result.total_size = partition.end_offset;\n                                    }\n                                }\n\n                                previous_partition_offset = Some(next_partition_offset);\n                                next_partition_offset += gpt_header[\"partition_entry_size\"];\n                            }\n\n                            if result.total_size > 0 {\n                                return Ok(result);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n#[derive(Debug, Default, Clone)]\nstruct GPTPartitionEntry {\n    pub end_offset: usize,\n    pub start_offset: usize,\n}\n\n/// Parse a GPT partition entry\nfn parse_gpt_partition_entry(entry_data: &[u8]) -> Option<GPTPartitionEntry> {\n    let entry_structure = vec![\n        (\"type_guid_p1\", \"u64\"),\n        (\"type_guid_p2\", \"u64\"),\n        (\"partition_guid_p1\", \"u64\"),\n        (\"partition_guid_p2\", \"u64\"),\n        (\"starting_lba\", \"u64\"),\n        (\"ending_lba\", \"u64\"),\n        (\"attributes\", \"u64\"),\n    ];\n\n    let mut result = GPTPartitionEntry {\n        ..Default::default()\n    };\n\n    if let Ok(entry_header) = common::parse(entry_data, &entry_structure, \"little\") {\n        // GUID types of NULL can be ignored\n        if entry_header[\"type_guid_p1\"] != 0 && entry_header[\"type_guid_p2\"] != 0 {\n            result.start_offset = lba_to_offset(entry_header[\"starting_lba\"]);\n            result.end_offset = lba_to_offset(entry_header[\"ending_lba\"]);\n            return Some(result);\n        }\n    }\n\n    None\n}\n\n// Convert LBA to offset\nfn lba_to_offset(lba: usize) -> usize {\n    lba * BLOCK_SIZE\n}\n"
  },
  {
    "path": "src/structures/elf.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Struct to store some useful ELF info\n#[derive(Debug, Default, Clone)]\npub struct ELFHeader {\n    pub class: String,\n    pub osabi: String,\n    pub machine: String,\n    pub exe_type: String,\n    pub endianness: String,\n}\n\n/// Partially parses an ELF header\npub fn parse_elf_header(elf_data: &[u8]) -> Result<ELFHeader, StructureError> {\n    const ELF_INFO_STRUCT_SIZE: usize = 8;\n    const ELF_IDENT_STRUCT_SIZE: usize = 16;\n\n    const EXPECTED_VERSION: usize = 1;\n\n    let elf_ident_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"class\", \"u8\"),\n        (\"endianness\", \"u8\"),\n        (\"version\", \"u8\"),\n        (\"osabi\", \"u8\"),\n        (\"abiversion\", \"u8\"),\n        (\"padding_1\", \"u32\"),\n        (\"padding_2\", \"u24\"),\n    ];\n\n    // Just enough of the ELF structure to grab some useful info\n    let elf_info_structure = vec![(\"type\", \"u16\"), (\"machine\", \"u16\"), (\"version\", \"u32\")];\n\n    let elf_classes = HashMap::from([(1, 32), (2, 64)]);\n\n    let elf_endianness = HashMap::from([(1, \"little\"), (2, \"big\")]);\n\n    let elf_osabi = HashMap::from([\n        (0, \"System-V (Unix)\"),\n        (1, \"HP-UX\"),\n        (2, \"NetBSD\"),\n        (3, \"Linux\"),\n        (4, \"GNU Hurd\"),\n        (5, \"86Open\"),\n        (6, \"Solaris\"),\n        (7, \"AIX\"),\n        (8, \"IRIX\"),\n        (9, \"FreeBSD\"),\n        (10, \"Tru64\"),\n        (11, \"Novell Modesto\"),\n        (12, \"OpenBSD\"),\n        (13, \"OpenVMS\"),\n        (14, \"NonStop Kernel\"),\n        (15, \"AROS\"),\n        (16, \"FenixOS\"),\n        (17, \"Nuxi CloudABI\"),\n        (18, \"OpenVOS\"),\n        (97, \"ARM ABI\"),\n        (102, \"Cell LV2\"),\n        (202, \"Cafe OS\"),\n        (255, \"embedded\"),\n    ]);\n\n    let elf_types = HashMap::from([\n        (0, \"no file type\"),\n        (1, \"relocatable\"),\n        (2, \"executable\"),\n        (3, \"shared object\"),\n        (4, \"core file\"),\n    ]);\n\n    let elf_machines = HashMap::from([\n        (0, \"no machine\"),\n        (1, \"AT&T WE 32100\"),\n        (2, \"SPARC\"),\n        (3, \"x86\"),\n        (4, \"Motorola 68k\"),\n        (5, \"Motorola 88k\"),\n        (6, \"Intel MCU\"),\n        (7, \"Intel 80860\"),\n        (8, \"MIPS\"),\n        (9, \"IBM System/370\"),\n        (10, \"MIPS RS3000\"),\n        (11, \"RS6000\"),\n        (15, \"HP PA-RISC\"),\n        (16, \"nCUBE\"),\n        (17, \"Fujitsu VPP500\"),\n        (18, \"SPARC32PLUS\"),\n        (19, \"Intel 80960\"),\n        (20, \"PowerPC\"),\n        (21, \"PowerPC 64-bit\"),\n        (22, \"S390\"),\n        (23, \"IBM SPU/SPC\"),\n        (24, \"cisco SVIP\"),\n        (25, \"cisco 7200\"),\n        (36, \"NEC V800\"),\n        (37, \"Fujitsu FR20\"),\n        (38, \"TRW RH-32\"),\n        (39, \"Motorola RCE\"),\n        (40, \"ARM\"),\n        (41, \"Digital Alpha\"),\n        (42, \"SuperH\"),\n        (43, \"SPARCv9\"),\n        (44, \"Siemens TriCore embedded processor\"),\n        (45, \"Argonaut RISC Core\"),\n        (46, \"Hitachi H8/300\"),\n        (47, \"Hitachi H8/300H\"),\n        (48, \"Hitachi H8S\"),\n        (49, \"Hitachi H8/500\"),\n        (50, \"IA-64\"),\n        (51, \"Stanford MIPS-X\"),\n        (52, \"Motorola ColdFire\"),\n        (53, \"Motorola M68HC12\"),\n        (54, \"Fujitsu MMA Multimedia Accelerator\"),\n        (55, \"Siemens PCP\"),\n        (56, \"Sony nCPU embedded RISC processor\"),\n        (57, \"Denso NDR1 microprocessor\"),\n        (58, \"Motorola StarCore\"),\n        (59, \"Toyota ME16\"),\n        (60, \"STMicroelectronics ST100\"),\n        (61, \"Advanced Logic TinyJ embedded processor\"),\n        (62, \"AMD X86-64\"),\n        (63, \"Sony DSP processor\"),\n        (64, \"PDP-10\"),\n        (65, \"PDP-11\"),\n        (66, \"Siemens FX66\"),\n        (67, \"STMicroelectronics ST9+\"),\n        (68, \"STMicroelectronics ST7\"),\n        (69, \"Motorola MC68HC16\"),\n        (70, \"Motorola MC68HC11\"),\n        (71, \"Motorola MC68HC08\"),\n        (72, \"Motorola MC68HC05\"),\n        (73, \"Silicon Graphics SVx\"),\n        (74, \"STMicroelectonrics ST19\"),\n        (75, \"Digital VAX\"),\n        (76, \"Axis Communications 32-bit CPU\"),\n        (77, \"Infineon Technologies 32-bit CPU\"),\n        (78, \"Element 14 64-bit DSP\"),\n        (79, \"LSI Logic 16-bit DSP\"),\n        (80, \"MMIX\"),\n        (81, \"Harvard machine-independent\"),\n        (82, \"SiTera Prism\"),\n        (83, \"Atmel AVR 8-bit\"),\n        (84, \"Fujitsu FR30\"),\n        (85, \"Mitsubishi D10V\"),\n        (86, \"Mitsubishi D30V\"),\n        (87, \"NEC v850\"),\n        (88, \"Renesas M32R\"),\n        (89, \"Matsushita MN10300\"),\n        (90, \"Matsushita MN10200\"),\n        (91, \"picoJava\"),\n        (92, \"OpenRISC\"),\n        (93, \"Synopsys ARCompact ARC700 cores\"),\n        (94, \"Tensilica Xtensa\"),\n        (95, \"Alphamosaic VideoCore\"),\n        (96, \"Thompson Multimedia\"),\n        (97, \"NatSemi 32k\"),\n        (98, \"Tenor Network TPC\"),\n        (99, \"Trebia SNP 1000\"),\n        (100, \"STMicroelectronics ST200\"),\n        (101, \"Ubicom IP2022\"),\n        (102, \"MAX Processor\"),\n        (103, \"NatSemi CompactRISC\"),\n        (104, \"Fujitsu F2MC16\"),\n        (105, \"TI msp430\"),\n        (106, \"Analog Devices Blackfin\"),\n        (107, \"S1C33 Family of Seiko Epson\"),\n        (108, \"Sharp embedded\"),\n        (109, \"Arca RISC\"),\n        (110, \"PKU-Unity Ltd.\"),\n        (111, \"eXcess: 16/32/64-bit\"),\n        (112, \"Icera Deep Execution Processor\"),\n        (113, \"Altera Nios II\"),\n        (114, \"NatSemi CRX\"),\n        (115, \"Motorola XGATE\"),\n        (116, \"Infineon C16x/XC16x\"),\n        (117, \"Renesas M16C series\"),\n        (118, \"Microchip dsPIC30F\"),\n        (119, \"Freescale RISC core\"),\n        (120, \"Renesas M32C series\"),\n        (131, \"Altium TSK3000 core\"),\n        (132, \"Freescale RS08\"),\n        (134, \"Cyan Technology eCOG2\"),\n        (135, \"Sunplus S+core7 RISC\"),\n        (136, \"New Japan Radio (NJR) 24-bit DSP\"),\n        (137, \"Broadcom VideoCore III processor\"),\n        (138, \"LatticeMico32\"),\n        (139, \"Seiko Epson C17 family\"),\n        (140, \"TMS320C6000\"),\n        (141, \"TMS320C2000\"),\n        (142, \"TMS320C55x\"),\n        (144, \"TI Programmable Realtime Unit\"),\n        (160, \"STMicroelectronics 64bit VLIW DSP\"),\n        (161, \"Cypress M8C\"),\n        (162, \"Renesas R32C series\"),\n        (163, \"NXP TriMedia family\"),\n        (164, \"Qualcomm DSP6\"),\n        (165, \"Intel 8051 and variants\"),\n        (166, \"STMicroelectronics STxP7x family\"),\n        (167, \"Andes embedded RISC\"),\n        (168, \"Cyan eCOG1X family\"),\n        (169, \"Dallas MAXQ30\"),\n        (170, \"New Japan Radio (NJR) 16-bit DSP\"),\n        (171, \"M2000 Reconfigurable RISC\"),\n        (172, \"Cray NV2 vector architecture\"),\n        (173, \"Renesas RX family\"),\n        (174, \"META\"),\n        (175, \"MCST Elbrus e2k\"),\n        (176, \"Cyan Technology eCOG16 family\"),\n        (177, \"NatSemi CompactRISC\"),\n        (178, \"Freescale Extended Time Processing Unit\"),\n        (179, \"Infineon SLE9X\"),\n        (180, \"Intel L1OM\"),\n        (181, \"Intel K1OM\"),\n        (183, \"ARM 64-bit\"),\n        (185, \"Atmel 32-bit family\"),\n        (186, \"STMicroeletronics STM8 8-bit\"),\n        (187, \"Tilera TILE64\"),\n        (188, \"Tilera TILEPro\"),\n        (189, \"Xilinx MicroBlaze 32-bit RISC\"),\n        (190, \"NVIDIA CUDA architecture\"),\n        (191, \"Tilera TILE-Gx\"),\n        (195, \"Synopsys ARCv2/HS3x/HS4x cores\"),\n        (197, \"Renesas RL78 family\"),\n        (199, \"Renesas 78K0R\"),\n        (200, \"Freescale 56800EX\"),\n        (201, \"Beyond BA1\"),\n        (202, \"Beyond BA2\"),\n        (203, \"XMOS xCORE\"),\n        (204, \"Microchip 8-bit PIC(r)\"),\n        (210, \"KM211 KM32\"),\n        (211, \"KM211 KMX32\"),\n        (212, \"KM211 KMX16\"),\n        (213, \"KM211 KMX8\"),\n        (214, \"KM211 KVARC\"),\n        (215, \"Paneve CDP\"),\n        (216, \"Cognitive Smart Memory\"),\n        (217, \"iCelero CoolEngine\"),\n        (218, \"Nanoradio Optimized RISC\"),\n        (219, \"CSR Kalimba architecture family\"),\n        (220, \"Zilog Z80\"),\n        (221, \"Controls and Data Services VISIUMcore processor\"),\n        (\n            222,\n            \"FTDI Chip FT32 high performance 32-bit RISC architecture\",\n        ),\n        (223, \"Moxie processor family\"),\n        (224, \"AMD GPU architecture\"),\n        (243, \"RISC-V\"),\n        (244, \"Lanai 32-bit processor\"),\n        (245, \"CEVA Processor Architecture Family\"),\n        (246, \"CEVA X2 Processor Family\"),\n        (247, \"Berkeley Packet Filter\"),\n        (248, \"Graphcore Intelligent Processing Unit\"),\n        (249, \"Imagination Technologies\"),\n        (250, \"Netronome Flow Processor\"),\n        (251, \"NEC Vector Engine\"),\n        (252, \"C-SKY processor family\"),\n        (253, \"Synopsys ARCv3 64-bit ISA/HS6x cores\"),\n        (254, \"MOS Technology MCS 6502 processor\"),\n        (255, \"Synopsys ARCv3 32-bit\"),\n        (256, \"Kalray VLIW core of the MPPA family\"),\n        (257, \"WDC 65C816\"),\n        (258, \"LoongArch\"),\n        (259, \"ChipON KungFu32\"),\n    ]);\n\n    let mut elf_hdr_info = ELFHeader {\n        ..Default::default()\n    };\n\n    // Endianness doesn't matter here, and we don't know what the ELF's endianness is yet\n    if let Ok(e_ident) = common::parse(elf_data, &elf_ident_structure, \"little\") {\n        // Sanity check the e_ident fields\n        if e_ident[\"padding_1\"] == 0\n            && e_ident[\"padding_2\"] == 0\n            && e_ident[\"version\"] == EXPECTED_VERSION\n            && elf_classes.contains_key(&e_ident[\"class\"])\n            && elf_osabi.contains_key(&e_ident[\"osabi\"])\n            && elf_endianness.contains_key(&e_ident[\"endianness\"])\n        {\n            // Set the ident info\n            elf_hdr_info.class = elf_classes[&e_ident[\"class\"]].to_string();\n            elf_hdr_info.osabi = elf_osabi[&e_ident[\"osabi\"]].to_string();\n            elf_hdr_info.endianness = elf_endianness[&e_ident[\"endianness\"]].to_string();\n\n            // The rest of the ELF info comes immediately after the ident structure\n            let elf_info_start: usize = ELF_IDENT_STRUCT_SIZE;\n            let elf_info_end: usize = elf_info_start + ELF_INFO_STRUCT_SIZE;\n\n            if let Some(elf_info_raw) = elf_data.get(elf_info_start..elf_info_end) {\n                // Parse the remaining info from the ELF header\n                if let Ok(elf_info) = common::parse(\n                    elf_info_raw,\n                    &elf_info_structure,\n                    elf_endianness[&e_ident[\"endianness\"]],\n                ) {\n                    // Sanity check the remaining ELF header fields\n                    if elf_info[\"version\"] == EXPECTED_VERSION\n                        && elf_types.contains_key(&elf_info[\"type\"])\n                    {\n                        // Set the ELF info fields\n                        elf_hdr_info.exe_type = elf_types[&elf_info[\"type\"]].to_string();\n                        elf_hdr_info.machine = elf_machines\n                            .get(&elf_info[\"machine\"])\n                            // Use 'Unknown' as a fallback for the machine type\n                            .unwrap_or(&\"Unknown\")\n                            .to_string();\n\n                        return Ok(elf_hdr_info);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/ext.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Expected size of an EXT superblock\npub const SUPERBLOCK_SIZE: usize = 1024;\n\n/// Expected file offset of an EXT superblock\npub const SUPERBLOCK_OFFSET: usize = 1024;\n\n/// Struct to store some useful EXT info\n#[derive(Debug, Default, Clone)]\npub struct EXTHeader {\n    pub os: String,\n    pub block_size: usize,\n    pub image_size: usize,\n    pub blocks_count: usize,\n    pub inodes_count: usize,\n    pub free_blocks_count: usize,\n    pub reserved_blocks_count: usize,\n}\n\n/// Partially parses an EXT superblock structure\npub fn parse_ext_header(ext_data: &[u8]) -> Result<EXTHeader, StructureError> {\n    // Max value of the EXT log block size\n    const MAX_BLOCK_LOG: usize = 2;\n\n    // Parital superblock structure, just enough for validation and size calculation\n    let ext_superblock_structure = vec![\n        (\"inodes_count\", \"u32\"),\n        (\"blocks_count\", \"u32\"),\n        (\"reserved_blocks_count\", \"u32\"),\n        (\"free_blocks_count\", \"u32\"),\n        (\"free_inodes_count\", \"u32\"),\n        (\"first_data_block\", \"u32\"),\n        (\"log_block_size\", \"u32\"),\n        (\"log_frag_size\", \"u32\"),\n        (\"blocks_per_group\", \"u32\"),\n        (\"frags_per_group\", \"u32\"),\n        (\"inodes_per_group\", \"u32\"),\n        (\"modification_time\", \"u32\"),\n        (\"write_time\", \"u32\"),\n        (\"mount_count\", \"u16\"),\n        (\"max_mount_count\", \"u16\"),\n        (\"magic\", \"u16\"),\n        (\"state\", \"u16\"),\n        (\"errors\", \"u16\"),\n        (\"s_minor_rev_level\", \"u16\"),\n        (\"last_check\", \"u32\"),\n        (\"check_interval\", \"u32\"),\n        (\"creator_os\", \"u32\"),\n        (\"s_rev_level\", \"u32\"),\n        (\"resuid\", \"u16\"),\n        (\"resgid\", \"u16\"),\n    ];\n\n    let allowed_rev_levels: Vec<usize> = vec![0, 1];\n    let allowed_first_data_blocks: Vec<usize> = vec![0, 1];\n\n    let supported_os: HashMap<usize, &str> = HashMap::from([\n        (0, \"Linux\"),\n        (1, \"GNU HURD\"),\n        (2, \"MASIX\"),\n        (3, \"FreeBSD\"),\n        (4, \"Lites\"),\n    ]);\n\n    let mut ext_header = EXTHeader {\n        ..Default::default()\n    };\n\n    // Sanity check the available data\n    if ext_data.len() >= (SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE) {\n        // Parse the EXT superblock structure\n        if let Ok(ext_superblock) = common::parse(\n            &ext_data[SUPERBLOCK_OFFSET..],\n            &ext_superblock_structure,\n            \"little\",\n        ) {\n            // Sanity check the reported OS this EXT image was created on\n            if supported_os.contains_key(&ext_superblock[\"creator_os\"]) {\n                // Sanity check the s_rev_level field\n                if allowed_rev_levels.contains(&ext_superblock[\"s_rev_level\"]) {\n                    // Sanity check the first_data_block field, which must be either 0 or 1\n                    if allowed_first_data_blocks.contains(&ext_superblock[\"first_data_block\"]) {\n                        // Santiy check the log_block_size\n                        if ext_superblock[\"log_block_size\"] <= MAX_BLOCK_LOG {\n                            // Update the reported image info\n                            ext_header.blocks_count = ext_superblock[\"blocks_count\"];\n                            ext_header.inodes_count = ext_superblock[\"inodes_count\"];\n                            ext_header.block_size = 1024 << ext_superblock[\"log_block_size\"];\n                            ext_header.free_blocks_count = ext_superblock[\"free_blocks_count\"];\n                            ext_header.os = supported_os[&ext_superblock[\"creator_os\"]].to_string();\n                            ext_header.reserved_blocks_count =\n                                ext_superblock[\"reserved_blocks_count\"];\n                            ext_header.image_size =\n                                ext_header.block_size * ext_superblock[\"blocks_count\"];\n\n                            return Ok(ext_header);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/fat.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store FAT header info\n#[derive(Debug, Default, Clone)]\npub struct FATHeader {\n    pub is_fat32: bool,\n    pub total_size: usize,\n}\n\n/// Parses a FAT header\npub fn parse_fat_header(fat_data: &[u8]) -> Result<FATHeader, StructureError> {\n    // Number of FATs could technically be 1 or greater, but *should* be 2\n    const EXPECTED_FAT_COUNT: usize = 2;\n\n    // http://elm-chan.org/docs/fat_e.html\n    let fat_boot_sector_structure = vec![\n        (\"opcode1\", \"u8\"),\n        (\"opcode2\", \"u8\"),\n        (\"opcode3\", \"u8\"),\n        (\"oem_name\", \"u64\"),\n        (\"bytes_per_sector\", \"u16\"),\n        (\"sectors_per_cluster\", \"u8\"),\n        (\"reserved_sectors\", \"u16\"),\n        (\"fat_count\", \"u8\"),\n        (\"root_entries_count_16\", \"u16\"),\n        (\"total_sectors_16\", \"u16\"),\n        (\"media_type\", \"u8\"),\n        (\"fat_size_16\", \"u16\"),\n        (\"sectors_per_track\", \"u16\"),\n        (\"number_of_heads\", \"u16\"),\n        (\"hidden_sectors\", \"u32\"),\n        (\"total_sectors_32\", \"u32\"),\n    ];\n\n    // First opcode should be jump instruction, either EB or E9\n    let valid_opcode1: Vec<usize> = vec![0xEB, 0xE9];\n\n    // bytes_per_sector must be one of these values\n    let valid_sector_sizes: Vec<usize> = vec![512, 1024, 2048, 4096];\n\n    // sectors_per_cluster must be one of these values\n    let valid_sectors_per_cluster: Vec<usize> = vec![1, 2, 4, 8, 16, 32, 64, 128];\n\n    // media_type must be one of these values\n    let valid_media_types: Vec<usize> = vec![0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE];\n\n    // Return value\n    let mut result = FATHeader {\n        ..Default::default()\n    };\n\n    // Parse the boot sector header\n    if let Ok(bs_header) = common::parse(fat_data, &fat_boot_sector_structure, \"little\") {\n        // Sanity check the first opcode\n        if valid_opcode1.contains(&bs_header[\"opcode1\"]) {\n            // Sanity check the reported sector size\n            if valid_sector_sizes.contains(&bs_header[\"bytes_per_sector\"]) {\n                // Sanity check the reported sectors per cluster\n                if valid_sectors_per_cluster.contains(&bs_header[\"sectors_per_cluster\"]) {\n                    // Reserved sectors must be at least 1\n                    if bs_header[\"reserved_sectors\"] > 0 {\n                        // Sanity check the reported number of FATs\n                        if bs_header[\"fat_count\"] == EXPECTED_FAT_COUNT {\n                            // Sanity check the reported media type\n                            if valid_media_types.contains(&bs_header[\"media_type\"]) {\n                                // This field is set to 0 for FAT32, but populated by FAT12/16\n                                result.is_fat32 = bs_header[\"fat_size_16\"] == 0;\n\n                                // total_sectors_16 is used for FAT12/16 that have less than 0x10000 sectors\n                                if bs_header[\"total_sectors_16\"] != 0 {\n                                    result.total_size = bs_header[\"total_sectors_16\"]\n                                        * bs_header[\"bytes_per_sector\"];\n                                // Else, total_sectors_32 is used to define the number of sectors\n                                } else {\n                                    result.total_size = bs_header[\"total_sectors_32\"]\n                                        * bs_header[\"bytes_per_sector\"];\n                                }\n\n                                // If both total_sectors_32 and total_sectors_16 is 0, this is not a valid FAT\n                                if result.total_size > 0 {\n                                    return Ok(result);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/gif.rs",
    "content": "use crate::common::is_offset_safe;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store GIF header info\n#[derive(Debug, Default, Clone)]\npub struct GIFHeader {\n    pub size: usize,\n    pub image_width: usize,\n    pub image_height: usize,\n}\n\n/// Parses a GIF header\npub fn parse_gif_header(gif_data: &[u8]) -> Result<GIFHeader, StructureError> {\n    let gif_header_structure = vec![\n        (\"magic_p1\", \"u24\"),\n        (\"magic_p2\", \"u24\"),\n        (\"image_width\", \"u16\"),\n        (\"image_height\", \"u16\"),\n        (\"flags\", \"u8\"),\n        (\"bg_color_index\", \"u8\"),\n        (\"aspect_ratio\", \"u8\"),\n    ];\n\n    // Parse the header\n    if let Ok(gif_header) = common::parse(gif_data, &gif_header_structure, \"little\") {\n        // Parse the flags to determine if a global color table is included in the header\n        let flags = parse_gif_flags(gif_header[\"flags\"]);\n\n        return Ok(GIFHeader {\n            size: common::size(&gif_header_structure) + flags.color_table_size,\n            image_width: gif_header[\"image_width\"],\n            image_height: gif_header[\"image_height\"],\n        });\n    }\n\n    Err(StructureError)\n}\n\n/// Struct to store GIF flags info\n#[derive(Debug, Default, Clone)]\npub struct GIFFlags {\n    /// Actual size of the color table, in bytes\n    pub color_table_size: usize,\n}\n\n/// Parses a GIF flag byte to determine the size of a color table, if any\nfn parse_gif_flags(flags: usize) -> GIFFlags {\n    const HAS_COLOR_TABLE: usize = 0x80;\n    const COLOR_TABLE_SIZE_MASK: usize = 0b111;\n\n    let mut retval = GIFFlags {\n        ..Default::default()\n    };\n\n    if (flags & HAS_COLOR_TABLE) != 0 {\n        let encoded_table_size = ((flags & COLOR_TABLE_SIZE_MASK) + 1) as u32;\n        retval.color_table_size = 3 * usize::pow(2, encoded_table_size);\n    }\n\n    retval\n}\n\n/// Parses an image descriptor; returns the total size of the descriptor and following image data\npub fn parse_gif_image_descriptor(gif_data: &[u8]) -> Result<usize, StructureError> {\n    const LZW_CODE_SIZE: usize = 1;\n\n    let img_desc_structure = vec![\n        (\"magic\", \"u8\"),\n        (\"image_left\", \"u16\"),\n        (\"image_top\", \"u16\"),\n        (\"image_width\", \"u16\"),\n        (\"image_height\", \"u16\"),\n        (\"flags\", \"u8\"),\n    ];\n\n    // Parse the image descriptor header\n    if let Ok(desc_header) = common::parse(gif_data, &img_desc_structure, \"little\") {\n        // Parse the flags field to determine if a local color table follows the header\n        let flags = parse_gif_flags(desc_header[\"flags\"]);\n        let mut total_size: usize = common::size(&img_desc_structure) + flags.color_table_size;\n\n        // After the header and optional color table will be a single-byte value representing the minimum LZW code size.\n        total_size += LZW_CODE_SIZE;\n\n        // An unspecified number of data sub-blocks follow.\n        if let Some(image_sub_blocks) = gif_data.get(total_size..) {\n            // Parse all sub-blocks to determine the total size of sub-blocks\n            if let Ok(sub_blocks_size) = parse_gif_sub_blocks(image_sub_blocks) {\n                total_size += sub_blocks_size;\n                return Ok(total_size);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parses all data sub blocks until a sub-block terminator byte is found.\n/// Returns the size, in bytes, of all sub-block data.\nfn parse_gif_sub_blocks(sub_block_data: &[u8]) -> Result<usize, StructureError> {\n    const SUB_BLOCK_TERMINATOR: u8 = 0;\n\n    let available_data = sub_block_data.len();\n    let mut next_offset = 0;\n    let mut previous_offset = None;\n\n    // Sub-blocks are just <u8 size of sub-block data><sub-block data>\n    while is_offset_safe(available_data, next_offset, previous_offset) {\n        match sub_block_data.get(next_offset) {\n            None => break,\n            Some(sub_block_size) => {\n                if *sub_block_size == SUB_BLOCK_TERMINATOR {\n                    return Ok(next_offset + 1);\n                } else {\n                    previous_offset = Some(next_offset);\n                    next_offset += (*sub_block_size as usize) + 1;\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parses a GIF extension block, returns the size of the extension block, in bytes.\npub fn parse_gif_extension(extension_data: &[u8]) -> Result<usize, StructureError> {\n    const PLAIN_TEXT: usize = 1;\n    const APPLICATION: usize = 0xFF;\n    const HEADER_SIZE: usize = 2;\n\n    // Some extensions do not include the sub_block_offset field;\n    // this field is always parsed here, but only used if applicable.\n    let extension_structure = vec![\n        (\"magic\", \"u8\"),\n        (\"extension_type\", \"u8\"),\n        (\"sub_block_offset\", \"u8\"),\n    ];\n\n    // Parse the extension header to get the extension sub-type\n    if let Ok(extension_header) = common::parse(extension_data, &extension_structure, \"little\") {\n        let ext_type = extension_header[\"extension_type\"];\n        let mut sub_blocks_offset: usize = HEADER_SIZE;\n\n        // These extensions have some extra data before the sub-blocks; all other extensions are just a 2-byte header followed by sub-blocks\n        if ext_type == APPLICATION || ext_type == PLAIN_TEXT {\n            sub_blocks_offset += extension_header[\"sub_block_offset\"] + 1;\n        }\n\n        // Parse all sub-block data to determine the total size of this extension block\n        if let Some(sub_block_data) = extension_data.get(sub_blocks_offset..) {\n            if let Ok(sub_blocks_size) = parse_gif_sub_blocks(sub_block_data) {\n                return Ok(sub_blocks_offset + sub_blocks_size);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/gzip.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Struct to store useful Gzip header info\n#[derive(Debug, Clone, Default)]\npub struct GzipHeader {\n    pub os: String,\n    pub size: usize,\n    pub comment: String,\n    pub timestamp: u32,\n    pub original_name: String,\n}\n\n/// Parses a Gzip file header\npub fn parse_gzip_header(header_data: &[u8]) -> Result<GzipHeader, StructureError> {\n    // Some expected constant values\n    const CRC_SIZE: usize = 2;\n    const NULL_BYTE_SIZE: usize = 1;\n    const DEFLATE_COMPRESSION: usize = 8;\n\n    const FLAG_CRC: usize = 0b0000_0010;\n    const FLAG_EXTRA: usize = 0b0000_0100;\n    const FLAG_NAME: usize = 0b0000_1000;\n    const FLAG_COMMENT: usize = 0b0001_0000;\n    const FLAG_RESERVED: usize = 0b1110_0000;\n\n    let gzip_header_structure = vec![\n        (\"magic\", \"u16\"),\n        (\"compression_method\", \"u8\"),\n        (\"flags\", \"u8\"),\n        (\"timestamp\", \"u32\"),\n        (\"extra_flags\", \"u8\"),\n        (\"osid\", \"u8\"),\n    ];\n\n    let gzip_extra_header_structure = vec![(\"id\", \"u16\"), (\"extra_data_len\", \"u16\")];\n\n    let known_os_ids: HashMap<usize, &str> = HashMap::from([\n        (0, \"FAT filesystem (MS-DOS, OS/2, NT/Win32\"),\n        (1, \"Amiga\"),\n        (2, \"VMS (or OpenVMS)\"),\n        (3, \"Unix\"),\n        (4, \"VM/CMS\"),\n        (5, \"Atari TOS\"),\n        (6, \"HPFS filesystem (OS/2, NT)\"),\n        (7, \"Macintosh\"),\n        (8, \"Z-System\"),\n        (9, \"CP/M\"),\n        (10, \"TOPS-20\"),\n        (11, \"NTFS filesystem (NT)\"),\n        (12, \"QDOS\"),\n        (13, \"Acorn RISCOS\"),\n        (255, \"unknown\"),\n    ]);\n\n    let mut header_info = GzipHeader {\n        ..Default::default()\n    };\n\n    // End of the fixed-size portion of the gzip header\n    header_info.size = common::size(&gzip_header_structure);\n\n    // Parse the gzip header\n    if let Ok(gzip_header) = common::parse(header_data, &gzip_header_structure, \"little\") {\n        // Report the timestamp\n        header_info.timestamp = gzip_header[\"timestamp\"] as u32;\n\n        // Sanity check; compression type should be deflate, reserved flag bits should not be set, OS ID should be a known value\n        if (gzip_header[\"flags\"] & FLAG_RESERVED) == 0\n            && gzip_header[\"compression_method\"] == DEFLATE_COMPRESSION\n            && known_os_ids.contains_key(&gzip_header[\"osid\"])\n        {\n            // Set the operating system string\n            header_info.os = known_os_ids[&gzip_header[\"osid\"]].to_string();\n\n            // Check if the optional \"extra\" data follows the standard Gzip header\n            if (gzip_header[\"flags\"] & FLAG_EXTRA) != 0 {\n                // File offsets and sizes for parsing the extra header\n                let extra_header_size = common::size(&gzip_extra_header_structure);\n                let extra_header_start: usize = header_info.size;\n                let extra_header_end: usize = extra_header_start + extra_header_size;\n\n                match header_data.get(extra_header_start..extra_header_end) {\n                    None => {\n                        return Err(StructureError);\n                    }\n                    Some(extra_header_data) => {\n                        // Parse the extra header and update the header_info.size to include this data\n                        match common::parse(\n                            extra_header_data,\n                            &gzip_extra_header_structure,\n                            \"little\",\n                        ) {\n                            Err(e) => {\n                                return Err(e);\n                            }\n                            Ok(extra_header) => {\n                                header_info.size +=\n                                    extra_header_size + extra_header[\"extra_data_len\"];\n                            }\n                        }\n                    }\n                }\n            }\n\n            // If the NULL-terminated original file name is included, it will be next\n            if (gzip_header[\"flags\"] & FLAG_NAME) != 0 {\n                match header_data.get(header_info.size..) {\n                    None => {\n                        return Err(StructureError);\n                    }\n                    Some(file_name_bytes) => {\n                        header_info.original_name = get_cstring(file_name_bytes);\n                        // The value returned by get_cstring does not include the terminating NULL byte\n                        header_info.size += header_info.original_name.len() + NULL_BYTE_SIZE;\n                    }\n                }\n            }\n\n            // If a NULL-terminated comment is included, it will be next\n            if (gzip_header[\"flags\"] & FLAG_COMMENT) != 0 {\n                match header_data.get(header_info.size..) {\n                    None => {\n                        return Err(StructureError);\n                    }\n                    Some(comment_bytes) => {\n                        header_info.comment = get_cstring(comment_bytes);\n                        // The value returned by get_cstring does not include the terminating NULL byte\n                        header_info.size += header_info.comment.len() + NULL_BYTE_SIZE;\n                    }\n                }\n            }\n\n            // Finally, a checksum field may be included\n            if (gzip_header[\"flags\"] & FLAG_CRC) != 0 {\n                header_info.size += CRC_SIZE;\n            }\n\n            // Deflate data should start at header_info.size; make sure this offset is sane\n            if header_data.len() >= header_info.size {\n                return Ok(header_info);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/iso9660.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store useful ISO info\n#[derive(Debug, Default, Clone)]\npub struct ISOHeader {\n    pub image_size: usize,\n}\n\n/// Partially parses an ISO header\npub fn parse_iso_header(iso_data: &[u8]) -> Result<ISOHeader, StructureError> {\n    // Offset from the beginning of the ISO image to the start of iso_structure\n    const ISO_STRUCT_START: usize = 32840;\n\n    // Partial ISO header structure, enough to reasonably validate that this is not a false positive and to calculate the total ISO size\n    let iso_structure = vec![\n        (\"unused1\", \"u64\"),\n        (\"volume_size_lsb\", \"u32\"),\n        (\"volume_size_msb\", \"u32\"),\n        (\"unused2\", \"u64\"),\n        (\"unused3\", \"u64\"),\n        (\"unused4\", \"u64\"),\n        (\"unused5\", \"u64\"),\n        (\"set_size_lsb\", \"u16\"),\n        (\"set_size_msb\", \"u16\"),\n        (\"sequence_number_lsb\", \"u16\"),\n        (\"sequence_number_msb\", \"u16\"),\n        (\"block_size_lsb\", \"u16\"),\n        (\"block_size_msb\", \"u16\"),\n        (\"path_table_size_lsb\", \"u32\"),\n        (\"path_table_size_msb\", \"u32\"),\n    ];\n\n    let mut iso_info = ISOHeader {\n        ..Default::default()\n    };\n\n    if let Some(iso_header_data) = iso_data.get(ISO_STRUCT_START..) {\n        // Parse the ISO header\n        if let Ok(iso_header) = common::parse(iso_header_data, &iso_structure, \"little\") {\n            // Make sure all the unused fields are, in fact, unused\n            if iso_header[\"unused1\"] == 0\n                && iso_header[\"unused2\"] == 0\n                && iso_header[\"unused3\"] == 0\n                && iso_header[\"unused4\"] == 0\n                && iso_header[\"unused5\"] == 0\n            {\n                /*\n                 * Make sure all the identical, but byte-swapped, fields agree.\n                 * NOTE: The to_be() conversions probably won't work on big-endian hosts.\n                 */\n                if iso_header[\"set_size_lsb\"]\n                    == (iso_header[\"set_size_msb\"] as u16).to_be() as usize\n                    && iso_header[\"block_size_lsb\"]\n                        == (iso_header[\"block_size_msb\"] as u16).to_be() as usize\n                    && iso_header[\"volume_size_lsb\"]\n                        == (iso_header[\"volume_size_msb\"] as u32).to_be() as usize\n                    && iso_header[\"sequence_number_lsb\"]\n                        == (iso_header[\"sequence_number_msb\"] as u16).to_be() as usize\n                    && iso_header[\"path_table_size_lsb\"]\n                        == (iso_header[\"path_table_size_msb\"] as u32).to_be() as usize\n                {\n                    iso_info.image_size =\n                        iso_header[\"volume_size_lsb\"] * iso_header[\"block_size_lsb\"];\n                    return Ok(iso_info);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/jboot.rs",
    "content": "use crate::common::{crc32, get_cstring};\nuse crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Struct to store JBOOT ARM firmware image info\n#[derive(Debug, Default, Clone)]\npub struct JBOOTArmHeader {\n    pub header_size: usize,\n    pub data_size: usize,\n    pub data_offset: usize,\n    pub erase_offset: usize,\n    pub erase_size: usize,\n    pub rom_id: String,\n}\n\n/// Parses a JBOOT ARM image header\npub fn parse_jboot_arm_header(jboot_data: &[u8]) -> Result<JBOOTArmHeader, StructureError> {\n    // Structure starts after 12-byte ROM ID\n    const STRUCTURE_OFFSET: usize = 12;\n\n    // Some expected header values\n    const LPVS_VALUE: usize = 1;\n    const MBZ_VALUE: usize = 0;\n    const HEADER_ID_VALUE: usize = 0x4842;\n    const HEADER_MAX_VERSION_VALUE: usize = 4;\n\n    let arm_structure = vec![\n        (\"drange\", \"u16\"),\n        (\"image_checksum\", \"u16\"),\n        (\"block_size\", \"u32\"),\n        (\"reserved2\", \"u32\"),\n        (\"reserved3\", \"u16\"),\n        (\"lpvs\", \"u8\"),\n        (\"mbz\", \"u8\"),\n        (\"timestamp\", \"u32\"),\n        (\"erase_start\", \"u32\"),\n        (\"erase_size\", \"u32\"),\n        (\"data_start\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"reserved4\", \"u32\"),\n        (\"reserved5\", \"u32\"),\n        (\"reserved6\", \"u32\"),\n        (\"reserved7\", \"u32\"),\n        (\"header_id\", \"u16\"),\n        (\"header_version\", \"u16\"),\n        (\"reserved8\", \"u16\"),\n        (\"section_id\", \"u8\"),\n        (\"image_info_type\", \"u8\"),\n        (\"image_info_offset\", \"u32\"),\n        (\"family\", \"u16\"),\n        (\"header_checksum\", \"u16\"),\n    ];\n\n    let structure_size: usize = common::size(&arm_structure);\n    let header_size: usize = structure_size + STRUCTURE_OFFSET;\n\n    if let Some(header_data) = jboot_data.get(STRUCTURE_OFFSET..) {\n        // Parse the header structure\n        if let Ok(arm_header) = common::parse(header_data, &arm_structure, \"little\") {\n            // Make sure the reserved fields are NULL\n            if arm_header[\"reserved2\"] == 0\n                && arm_header[\"reserved3\"] == 0\n                && arm_header[\"reserved4\"] == 0\n                && arm_header[\"reserved5\"] == 0\n                && arm_header[\"reserved6\"] == 0\n                && arm_header[\"reserved7\"] == 0\n                && arm_header[\"reserved8\"] == 0\n            {\n                // Sanity check expected header values\n                if arm_header[\"lpvs\"] == LPVS_VALUE\n                    && arm_header[\"mbz\"] == MBZ_VALUE\n                    && arm_header[\"header_id\"] == HEADER_ID_VALUE\n                    && arm_header[\"header_version\"] <= HEADER_MAX_VERSION_VALUE\n                {\n                    return Ok(JBOOTArmHeader {\n                        header_size,\n                        rom_id: get_cstring(&jboot_data[0..STRUCTURE_OFFSET]),\n                        data_size: arm_header[\"data_size\"],\n                        data_offset: arm_header[\"data_start\"],\n                        erase_offset: arm_header[\"erase_start\"],\n                        erase_size: arm_header[\"erase_size\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about JBOOT STAG headers\n#[derive(Debug, Default, Clone)]\npub struct JBOOTStagHeader {\n    pub header_size: usize,\n    pub image_size: usize,\n    pub is_factory_image: bool,\n    pub is_sysupgrade_image: bool,\n}\n\n/// Parses a JBOOT STAG header\npub fn parse_jboot_stag_header(jboot_data: &[u8]) -> Result<JBOOTStagHeader, StructureError> {\n    // cmark value for factory images; for system upgrade images, cmark must equal id\n    const FACTORY_IMAGE_TYPE: usize = 0xFF;\n\n    let stag_structure = vec![\n        (\"cmark\", \"u8\"),\n        (\"id\", \"u8\"),\n        (\"magic\", \"u16\"),\n        (\"timestamp\", \"u32\"),\n        (\"image_size\", \"u32\"),\n        (\"image_checksum\", \"u16\"),\n        (\"header_checksum\", \"u16\"),\n    ];\n\n    let mut result = JBOOTStagHeader {\n        ..Default::default()\n    };\n\n    // Parse the header structure\n    if let Ok(stag_header) = common::parse(jboot_data, &stag_structure, \"little\") {\n        result.header_size = common::size(&stag_structure);\n        result.image_size = stag_header[\"image_size\"];\n\n        if result.image_size > result.header_size {\n            result.is_factory_image = stag_header[\"cmark\"] == FACTORY_IMAGE_TYPE;\n            result.is_sysupgrade_image = stag_header[\"cmark\"] == stag_header[\"id\"];\n\n            if result.is_factory_image || result.is_sysupgrade_image {\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n#[derive(Default, Debug, Clone)]\npub struct JBOOTSchHeader {\n    pub header_size: usize,\n    pub compression: String,\n    pub kernel_size: usize,\n    pub kernel_entry_point: usize,\n    pub kernel_checksum: usize,\n}\n\n/// Parses a JBOOT SCH2 header\npub fn parse_jboot_sch2_header(jboot_data: &[u8]) -> Result<JBOOTSchHeader, StructureError> {\n    const VERSION_VALUE: usize = 2;\n\n    let sch2_structure = vec![\n        (\"magic\", \"u16\"),\n        (\"compression_type\", \"u8\"),\n        (\"version\", \"u8\"),\n        (\"ram_entry_address\", \"u32\"),\n        (\"kernel_image_size\", \"u32\"),\n        (\"kernel_image_crc\", \"u32\"),\n        (\"ram_start_address\", \"u32\"),\n        (\"rootfs_flash_address\", \"u32\"),\n        (\"rootfs_size\", \"u32\"),\n        (\"rootfs_crc\", \"u32\"),\n        (\"header_crc\", \"u32\"),\n        (\"header_size\", \"u16\"),\n        (\"cmd_line_size\", \"u16\"),\n    ];\n\n    let compression_types: HashMap<usize, &str> =\n        HashMap::from([(0, \"none\"), (1, \"jz\"), (2, \"gzip\"), (3, \"lzma\")]);\n\n    let mut result = JBOOTSchHeader {\n        header_size: common::size(&sch2_structure),\n        ..Default::default()\n    };\n\n    if let Ok(sch2_header) = common::parse(jboot_data, &sch2_structure, \"little\") {\n        // Sanity check some header fields\n        if sch2_header[\"version\"] == VERSION_VALUE\n            && sch2_header[\"header_size\"] == result.header_size\n            && compression_types.contains_key(&sch2_header[\"compression_type\"])\n        {\n            // Validate the header checksum\n            if let Some(header_bytes) = jboot_data.get(0..sch2_header[\"header_size\"]) {\n                if sch2_header_crc(header_bytes) == sch2_header[\"header_crc\"] {\n                    result.compression =\n                        compression_types[&sch2_header[\"compression_type\"]].to_string();\n                    result.kernel_checksum = sch2_header[\"kernel_image_crc\"];\n                    result.kernel_size = sch2_header[\"kernel_image_size\"];\n                    result.kernel_entry_point = sch2_header[\"ram_entry_address\"];\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Calculate a JBOOT SCH2 header CRC\nfn sch2_header_crc(sch2_header_bytes: &[u8]) -> usize {\n    // Start and end offsets of the header CRC field\n    const HEADER_CRC_START: usize = 32;\n    const HEADER_CRC_END: usize = 36;\n\n    let mut crc: usize = 0;\n\n    if sch2_header_bytes.len() > HEADER_CRC_END {\n        let mut crc_data: Vec<u8> = sch2_header_bytes.to_vec();\n\n        // Header CRC field has to be NULL'd out\n        for crc_byte in crc_data\n            .iter_mut()\n            .take(HEADER_CRC_END)\n            .skip(HEADER_CRC_START)\n        {\n            *crc_byte = 0;\n        }\n\n        crc = crc32(&crc_data) as usize;\n    }\n\n    crc\n}\n"
  },
  {
    "path": "src/structures/jffs2.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse crc32_v2;\n\n/// JFFS2 node header size\npub const JFFS2_NODE_STRUCT_SIZE: usize = 12;\n\n/// Structure for storing useful JFFS node info\n#[derive(Debug, Default, Clone)]\npub struct JFFS2Node {\n    pub size: usize,\n    pub node_type: u16,\n    pub endianness: String,\n}\n\n/// Parse a JFFS2 node header\npub fn parse_jffs2_node_header(node_data: &[u8]) -> Result<JFFS2Node, StructureError> {\n    // Expected JFFS2 node magic\n    const JFFS2_CORRECT_MAGIC: usize = 0x1985;\n\n    // Number of header bytes over which the header CRC is calculated\n    const JFFS2_HEADER_CRC_SIZE: usize = 8;\n\n    let jffs2_node_structure = vec![\n        (\"magic\", \"u16\"),\n        (\"type\", \"u16\"),\n        (\"size\", \"u32\"),\n        (\"crc\", \"u32\"),\n    ];\n\n    let mut node = JFFS2Node {\n        ..Default::default()\n    };\n\n    // Try little endian first\n    node.endianness = \"little\".to_string();\n\n    // Parse the node header\n    if let Ok(mut node_header) = common::parse(node_data, &jffs2_node_structure, &node.endianness) {\n        // If the node header magic isn't correct, try parsing the header as big endian\n        if node_header[\"magic\"] != JFFS2_CORRECT_MAGIC {\n            node.endianness = \"big\".to_string();\n            match common::parse(node_data, &jffs2_node_structure, &node.endianness) {\n                Err(_) => {\n                    return Err(StructureError);\n                }\n                Ok(node_header_be) => {\n                    node_header = node_header_be.clone();\n                }\n            }\n        }\n\n        // Node magic must be correct at this point, else this node is invalid\n        if node_header[\"magic\"] == JFFS2_CORRECT_MAGIC {\n            // Calculate the node header CRC\n            let node_calculated_crc = jffs2_node_crc(&node_data[0..JFFS2_HEADER_CRC_SIZE]);\n\n            // Validate the node header CRC\n            if node_calculated_crc == node_header[\"crc\"] {\n                node.size = node_header[\"size\"];\n                node.node_type = node_header[\"type\"] as u16;\n                return Ok(node);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// CRC calculation for JFFS\nfn jffs2_node_crc(file_data: &[u8]) -> usize {\n    (crc32_v2::crc32(0xFFFFFFFF, file_data) ^ 0xFFFFFFFF) as usize\n}\n"
  },
  {
    "path": "src/structures/linux.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store linux ARM64 boot image header info\n#[derive(Debug, Default, Clone)]\npub struct LinuxARM64BootHeader {\n    pub header_size: usize,\n    pub image_size: usize,\n    pub endianness: String,\n}\n\n/// Struct to store Linux ARM zImage info\n#[derive(Debug, Default, Clone)]\npub struct LinuxARMzImageHeader {\n    pub endianness: String,\n}\n\n/// Parses a Linux ARM zImage header\npub fn parse_linux_arm_zimage_header(\n    zimage_data: &[u8],\n) -> Result<LinuxARMzImageHeader, StructureError> {\n    const NOP_LE: usize = 0xE1A00000;\n    const NOP_BE: usize = 0x0000A0E1;\n\n    let zimage_structure = vec![\n        (\"nop1\", \"u32\"),\n        (\"nop2\", \"u32\"),\n        (\"nop3\", \"u32\"),\n        (\"nop4\", \"u32\"),\n        (\"nop5\", \"u32\"),\n        (\"nop6\", \"u32\"),\n        (\"nop7\", \"u32\"),\n        (\"nop8\", \"u32\"),\n    ];\n\n    if let Ok(zimage_nops) = common::parse(zimage_data, &zimage_structure, \"little\") {\n        if zimage_nops[\"nop1\"] == zimage_nops[\"nop2\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop3\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop4\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop5\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop6\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop7\"]\n            && zimage_nops[\"nop1\"] == zimage_nops[\"nop8\"]\n        {\n            if zimage_nops[\"nop1\"] == NOP_LE {\n                return Ok(LinuxARMzImageHeader {\n                    endianness: \"little\".to_string(),\n                });\n            }\n\n            if zimage_nops[\"nop1\"] == NOP_BE {\n                return Ok(LinuxARMzImageHeader {\n                    endianness: \"big\".to_string(),\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parses a linux ARM64 boot header\npub fn parse_linux_arm64_boot_image_header(\n    img_data: &[u8],\n) -> Result<LinuxARM64BootHeader, StructureError> {\n    const PE: &[u8] = b\"PE\";\n    const FLAGS_RESERVED_MASK: usize =\n        0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11110000;\n    const FLAGS_ENDIAN_MASK: usize = 1;\n    const BIG_ENDIAN: usize = 1;\n\n    // https://www.kernel.org/doc/Documentation/arm64/booting.txt\n    let boot_img_structure = vec![\n        (\"code0\", \"u32\"),\n        (\"code1\", \"u32\"),\n        (\"image_load_offset\", \"u64\"),\n        (\"image_size\", \"u64\"),\n        (\"flags\", \"u64\"),\n        (\"reserved1\", \"u64\"),\n        (\"reserved2\", \"u64\"),\n        (\"reserved3\", \"u64\"),\n        (\"magic\", \"u32\"),\n        (\"pe_offset\", \"u32\"),\n    ];\n\n    let mut result = LinuxARM64BootHeader {\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(img_header) = common::parse(img_data, &boot_img_structure, \"little\") {\n        // Make sure the reserved fields are not set\n        if img_header[\"reserved1\"] == 0\n            && img_header[\"reserved2\"] == 0\n            && img_header[\"reserved3\"] == 0\n        {\n            // Start and end of PE signature\n            let pe_start = img_header[\"pe_offset\"];\n            let pe_end = pe_start + PE.len();\n\n            // Get the data pointed to by the pe_offset header field\n            if let Some(pe_data) = img_data.get(pe_start..pe_end) {\n                // There should be a PE header here\n                if pe_data == PE {\n                    // Make sure the reserved flag bits are not set\n                    if (img_header[\"flags\"] & FLAGS_RESERVED_MASK) == 0 {\n                        // Determine the endianness from the flags field\n                        if (img_header[\"flags\"] & FLAGS_ENDIAN_MASK) == BIG_ENDIAN {\n                            result.endianness = \"big\".to_string();\n                        } else {\n                            result.endianness = \"little\".to_string();\n                        }\n\n                        // Report the kernel image and header sizes\n                        result.image_size = img_header[\"image_size\"];\n                        result.header_size = common::size(&boot_img_structure);\n\n                        return Ok(result);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/logfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Offset of the LogFS magic bytes from the start of the file system\npub const LOGFS_MAGIC_OFFSET: usize = 0x18;\n\n/// Struct to store LogFS info\n#[derive(Debug, Default, Clone)]\npub struct LogFSSuperBlock {\n    pub total_size: usize,\n}\n\n/// Parses a LogFS superblock\npub fn parse_logfs_super_block(logfs_data: &[u8]) -> Result<LogFSSuperBlock, StructureError> {\n    //const LOGFS_CRC_START: usize = LOGFS_MAGIC_OFFSET + 12;\n    //const LOGFS_CRC_END: usize = 256;\n\n    let logfs_sb_structure = vec![\n        (\"magic\", \"u64\"),\n        (\"crc32\", \"u32\"),\n        (\"ifile_levels\", \"u8\"),\n        (\"iblock_levels\", \"u8\"),\n        (\"data_levels\", \"u8\"),\n        (\"segment_shift\", \"u8\"),\n        (\"block_shift\", \"u8\"),\n        (\"write_shift\", \"u8\"),\n        (\"pad0\", \"u32\"),\n        (\"pad1\", \"u16\"),\n        (\"filesystem_size\", \"u64\"),\n        (\"segment_size\", \"u32\"),\n        (\"bad_seg_reserved\", \"u32\"),\n        (\"feature_incompat\", \"u64\"),\n        (\"feature_ro_compat\", \"u64\"),\n        (\"feature_compat\", \"u64\"),\n        (\"feature_flags\", \"u64\"),\n        (\"root_reserve\", \"u64\"),\n        (\"speed_reserve\", \"u64\"),\n    ];\n\n    if let Some(sb_struct_data) = logfs_data.get(LOGFS_MAGIC_OFFSET..) {\n        if let Ok(super_block) = common::parse(sb_struct_data, &logfs_sb_structure, \"big\") {\n            if super_block[\"pad0\"] == 0 && super_block[\"pad1\"] == 0 {\n                return Ok(LogFSSuperBlock {\n                    total_size: super_block[\"filesystem_size\"],\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/luks.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store some useful LUKS info\n#[derive(Debug, Default, Clone)]\npub struct LUKSHeader {\n    pub version: usize,\n    pub header_size: usize,\n    pub hashfn: String,\n    pub cipher_mode: String,\n    pub cipher_algorithm: String,\n}\n\n/// Partially parses a LUKS header\npub fn parse_luks_header(luks_data: &[u8]) -> Result<LUKSHeader, StructureError> {\n    // Start and end offsets of the cipher algorithm string\n    const CIPHER_ALGO_START: usize = 8;\n    const CIPHER_ALGO_END: usize = 40;\n\n    // Start and end offsets of the cipher mode string\n    const CIPHER_MODE_START: usize = 40;\n    const CIPHER_MODE_END: usize = 72;\n\n    // Start and end offsets of the hash function string\n    const HASHFN_START: usize = 72;\n    const HASHFN_END: usize = 104;\n\n    // Minimum LUKS2 header size (assuming no JSON data)\n    const LUKS2_MIN_HEADER_SIZE: usize = 4032;\n\n    // https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup\n    // https://vhs.codeberg.page/post/external-backup-drive-encryption/assets/luks2_doc_wip.pdf\n    let luks_base_structure = vec![\n        (\"magic_1\", \"u32\"),\n        (\"magic_2\", \"u16\"),\n        (\"version\", \"u16\"),\n        (\"header_size\", \"u64\"), // Only available in LUKS2\n    ];\n\n    let mut luks_hdr_info = LUKSHeader {\n        ..Default::default()\n    };\n\n    if let Ok(luks_base) = common::parse(luks_data, &luks_base_structure, \"big\") {\n        luks_hdr_info.version = luks_base[\"version\"];\n\n        // Both v1 and v2 include the hash function string at the same offset\n        if let Some(hashfn_bytes) = luks_data.get(HASHFN_START..HASHFN_END) {\n            luks_hdr_info.hashfn = get_cstring(hashfn_bytes);\n\n            // Make sure there was actually a string at the expected hash function offset\n            if !luks_hdr_info.hashfn.is_empty() {\n                // Need to process v1 and v2 headers differently\n                if luks_hdr_info.version == 1 {\n                    // Get the cipher algorithm string\n                    if let Some(cipher_algo_bytes) =\n                        luks_data.get(CIPHER_ALGO_START..CIPHER_ALGO_END)\n                    {\n                        luks_hdr_info.cipher_algorithm = get_cstring(cipher_algo_bytes);\n\n                        // Get the cipher mode string\n                        if let Some(cipher_mode_bytes) =\n                            luks_data.get(CIPHER_MODE_START..CIPHER_MODE_END)\n                        {\n                            luks_hdr_info.cipher_mode = get_cstring(cipher_mode_bytes);\n\n                            // Make sure there were valid strings specified for both cipher algo and cipher mode\n                            if !luks_hdr_info.cipher_mode.is_empty()\n                                && !luks_hdr_info.cipher_algorithm.is_empty()\n                            {\n                                return Ok(luks_hdr_info);\n                            }\n                        }\n                    }\n                } else if luks_hdr_info.version == 2 {\n                    // v2 doesn't have the same string entries, but does include a header size\n                    luks_hdr_info.header_size = luks_base[\"header_size\"];\n\n                    // Sanity check the header size\n                    if luks_hdr_info.header_size > LUKS2_MIN_HEADER_SIZE\n                        && luks_hdr_info.header_size < luks_data.len()\n                    {\n                        return Ok(luks_hdr_info);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/lz4.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse xxhash_rust;\n\n/// Struct to store LZ4 file header info\n#[derive(Debug, Default, Clone)]\npub struct LZ4FileHeader {\n    pub header_size: usize,\n    pub block_checksum_present: bool,\n    pub content_checksum_present: bool,\n}\n\n/// Parse an LZ4 file header\npub fn parse_lz4_file_header(lz4_data: &[u8]) -> Result<LZ4FileHeader, StructureError> {\n    // Fixed size constants\n    const MAGIC_SIZE: usize = 4;\n    const LZ4_STRUCT_SIZE: usize = 6;\n\n    const BD_RESERVED_MASK: usize = 0b10001111;\n    const FLAGS_RESERVED_MASK: usize = 0b00000010;\n\n    const FLAG_DICTIONARY_PRESENT: usize = 0b00000001;\n    const FLAG_CONTENT_SIZE_PRESENT: usize = 0b00001000;\n    const FLAG_BLOCK_CHECKSUM_PRESENT: usize = 0b00010000;\n    const FLAG_CONTENT_CHECKSUM_PRESENT: usize = 0b00000100;\n\n    const DICTIONARY_LEN: usize = 4;\n    const CONTENT_SIZE_LEN: usize = 8;\n\n    // Basic LZ4 header; optional fields and header CRC byte follow\n    let lz4_structure = vec![(\"magic\", \"u32\"), (\"flags\", \"u8\"), (\"bd\", \"u8\")];\n\n    let mut lz4_hdr_info = LZ4FileHeader {\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(lz4_header) = common::parse(lz4_data, &lz4_structure, \"little\") {\n        // Make sure the reserved bits aren't set\n        if (lz4_header[\"flags\"] & FLAGS_RESERVED_MASK) == 0\n            && (lz4_header[\"bd\"] & BD_RESERVED_MASK) == 0\n        {\n            /*\n             * Calculate the start and end of data used to calculate the header CRC.\n             * CRC is calculated over the entire descriptor frame, including optional fields,\n             * but does not include the magic bytes.\n             */\n            let crc_data_start: usize = MAGIC_SIZE;\n            let mut crc_data_end: usize = crc_data_start + (LZ4_STRUCT_SIZE - MAGIC_SIZE);\n\n            // If the optional content size field is present, the CRC field is pushed back after the content size field\n            if (lz4_header[\"flags\"] & FLAG_CONTENT_SIZE_PRESENT) != 0 {\n                crc_data_end += CONTENT_SIZE_LEN;\n            }\n\n            // If the optional dictionary ID field is present, the CRC field is pushed back after the dictionary ID field\n            if (lz4_header[\"flags\"] & FLAG_DICTIONARY_PRESENT) != 0 {\n                crc_data_end += DICTIONARY_LEN;\n            }\n\n            // Get the data over which the CRC is calculated\n            if let Some(crc_data) = lz4_data.get(crc_data_start..crc_data_end) {\n                // Grab the header CRC value stored in the file header (one byte only)\n                if let Some(actual_crc) = lz4_data.get(crc_data_end) {\n                    // Calculate the header CRC, which is the second byte of the xxh32 hash. It is calculated over the header, excluding the magic bytes.\n                    let calculated_crc: u8 =\n                        ((xxhash_rust::xxh32::xxh32(crc_data, 0) >> 8) & 0xFF) as u8;\n\n                    // Make sure the CRC's match\n                    if *actual_crc == calculated_crc {\n                        // Data blocks start immediately after the header checksum byte\n                        lz4_hdr_info.header_size = crc_data_end + 1;\n                        lz4_hdr_info.block_checksum_present =\n                            (lz4_header[\"flags\"] & FLAG_BLOCK_CHECKSUM_PRESENT) != 0;\n                        lz4_hdr_info.content_checksum_present =\n                            (lz4_header[\"flags\"] & FLAG_CONTENT_CHECKSUM_PRESENT) != 0;\n\n                        return Ok(lz4_hdr_info);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Struct to store LZ4 block header info\n#[derive(Debug, Default, Clone)]\npub struct LZ4BlockHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n    pub checksum_size: usize,\n    pub last_block: bool,\n}\n\n/// Parse an LZ4 block header\npub fn parse_lz4_block_header(\n    lz4_block_data: &[u8],\n    checksum_present: bool,\n) -> Result<LZ4BlockHeader, StructureError> {\n    // Useful constants\n    const SIZE_MASK: u32 = 0x7FFFFFFF;\n    const END_MARKER: usize = 0;\n    const CHECKSUM_SIZE: usize = 4;\n    const BLOCK_STRUCT_SIZE: usize = 4;\n\n    // Block headers are just a u32 size field\n    let block_structure = vec![(\"block_size\", \"u32\")];\n\n    let mut lz4_block = LZ4BlockHeader {\n        ..Default::default()\n    };\n\n    // Parse the block header\n    if let Ok(block_header) = common::parse(lz4_block_data, &block_structure, \"little\") {\n        // Header size is always 4 bytes\n        lz4_block.header_size = BLOCK_STRUCT_SIZE;\n\n        // If file size is 0, this is the end of the LZ4 data\n        lz4_block.last_block = block_header[\"block_size\"] == END_MARKER;\n\n        // If a checksum is present, it will be an extra 4 bytes at the end of the block\n        if checksum_present {\n            lz4_block.checksum_size = CHECKSUM_SIZE;\n        }\n\n        // The high bit of the reported block size is not part of the actual block size\n        lz4_block.data_size = ((block_header[\"block_size\"] as u32) & SIZE_MASK) as usize;\n\n        return Ok(lz4_block);\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/lzfse.rs",
    "content": "use crate::structures::common::{StructureError, parse};\n\n/// Struct to store LZFSE block info\n#[derive(Debug, Default, Clone)]\npub struct LZFSEBlock {\n    pub eof: bool,\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parse an LZFSE block header\npub fn parse_lzfse_block_header(lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    // LZFSE block types\n    const ENDOFSTREAM: usize = 0x24787662;\n    const UNCOMPRESSED: usize = 0x2d787662;\n    const COMPRESSEDV1: usize = 0x31787662;\n    const COMPRESSEDV2: usize = 0x32787662;\n    const COMPRESSEDLZVN: usize = 0x6e787662;\n\n    // Each block starts with a 4-byte magic identifier\n    let block_type_structure = vec![(\"block_type\", \"u32\")];\n\n    // Parse the block header\n    if let Ok(block_type_header) = parse(lzfse_data, &block_type_structure, \"little\") {\n        let block_type = block_type_header[\"block_type\"];\n\n        // Block headers are different for different block types; process this block header accordingly\n        if block_type == ENDOFSTREAM {\n            return parse_endofstream_block_header(lzfse_data);\n        } else if block_type == UNCOMPRESSED {\n            return parse_uncompressed_block_header(lzfse_data);\n        } else if block_type == COMPRESSEDV1 {\n            return parse_compressedv1_block_header(lzfse_data);\n        } else if block_type == COMPRESSEDV2 {\n            return parse_compressedv2_block_header(lzfse_data);\n        } else if block_type == COMPRESSEDLZVN {\n            return parse_compressedlzvn_block_header(lzfse_data);\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parse an end-of-stream LZFSE block header\nfn parse_endofstream_block_header(_lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    // This is easy; it's just the 4-byte magic bytes marking the end-of-stream\n    Ok(LZFSEBlock {\n        eof: true,\n        data_size: 0,\n        header_size: 4,\n    })\n}\n\n/// Parse an uncompressed LZFSE block header\nfn parse_uncompressed_block_header(lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    const HEADER_SIZE: usize = 8;\n\n    let block_structure = vec![(\"magic\", \"u32\"), (\"n_raw_bytes\", \"u32\")];\n\n    if let Ok(header) = parse(lzfse_data, &block_structure, \"little\") {\n        return Ok(LZFSEBlock {\n            eof: false,\n            data_size: header[\"n_raw_bytes\"],\n            header_size: HEADER_SIZE,\n        });\n    }\n\n    Err(StructureError)\n}\n\n/// Parse a compressed (version 1) LZFSE block header\nfn parse_compressedv1_block_header(lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    const HEADER_SIZE: usize = 770;\n\n    let block_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"n_raw_bytes\", \"u32\"),\n        (\"n_payload_bytes\", \"u32\"),\n        (\"n_literals\", \"u32\"),\n        (\"n_matches\", \"u32\"),\n        (\"n_literal_payload_bytes\", \"u32\"),\n        (\"n_lmd_payload_bytes\", \"u32\"),\n        (\"literal_bits\", \"u32\"),\n        (\"literal_state\", \"u64\"),\n        (\"lmd_bits\", \"u32\"),\n        (\"l_state\", \"u16\"),\n        (\"m_state\", \"u16\"),\n        (\"d_state\", \"u16\"),\n        // Frequency tables follow\n    ];\n\n    if let Ok(header) = parse(lzfse_data, &block_structure, \"little\") {\n        return Ok(LZFSEBlock {\n            eof: false,\n            data_size: header[\"n_literal_payload_bytes\"] + header[\"n_lmd_payload_bytes\"],\n            header_size: HEADER_SIZE,\n        });\n    }\n\n    Err(StructureError)\n}\n\n/// Parse a compressed (version 2) LZFSE block header\nfn parse_compressedv2_block_header(lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    const N_PAYLOAD_SHIFT: usize = 20;\n    const LMD_PAYLOAD_SHIFT: usize = 40;\n    const PAYLOAD_MASK: usize = 0b11111_11111_11111_11111;\n\n    let block_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"uncompressed_size\", \"u32\"),\n        (\"packed_field_1\", \"u64\"),\n        (\"packed_field_2\", \"u64\"),\n        (\"header_size\", \"u32\"),\n        (\"state_fields\", \"u32\"),\n        // Variable length header field follows\n    ];\n\n    if let Ok(block_header) = parse(lzfse_data, &block_structure, \"little\") {\n        let n_lmd_payload_bytes =\n            (block_header[\"packed_field_2\"] >> LMD_PAYLOAD_SHIFT) & PAYLOAD_MASK;\n        let n_literal_payload_bytes =\n            (block_header[\"packed_field_1\"] >> N_PAYLOAD_SHIFT) & PAYLOAD_MASK;\n\n        return Ok(LZFSEBlock {\n            eof: false,\n            data_size: n_lmd_payload_bytes + n_literal_payload_bytes,\n            header_size: block_header[\"header_size\"],\n        });\n    }\n\n    Err(StructureError)\n}\n\n/// Parse a LZVN compressed LZFSE block header\nfn parse_compressedlzvn_block_header(lzfse_data: &[u8]) -> Result<LZFSEBlock, StructureError> {\n    const HEADER_SIZE: usize = 12;\n\n    let block_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"n_raw_bytes\", \"u32\"),\n        (\"n_payload_bytes\", \"u32\"),\n    ];\n\n    if let Ok(header) = parse(lzfse_data, &block_structure, \"little\") {\n        return Ok(LZFSEBlock {\n            eof: false,\n            data_size: header[\"n_payload_bytes\"],\n            header_size: HEADER_SIZE,\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/lzma.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store useful LZMA header data\n#[derive(Debug, Default, Clone)]\npub struct LZMAHeader {\n    pub properties: usize,\n    pub dictionary_size: usize,\n    pub decompressed_size: usize,\n}\n\n/// Parse an LZMA header\npub fn parse_lzma_header(lzma_data: &[u8]) -> Result<LZMAHeader, StructureError> {\n    // Streamed data has a reported size of -1\n    const LZMA_STREAM_SIZE: usize = 0xFFFFFFFFFFFFFFFF;\n\n    // Some sane min and max values on the reported decompressed data size\n    const MIN_SUPPORTED_DECOMPRESSED_SIZE: usize = 256;\n    const MAX_SUPPORTED_DECOMPRESSED_SIZE: usize = 0xFFFFFFFF;\n\n    let lzma_structure = vec![\n        (\"properties\", \"u8\"),\n        (\"dictionary_size\", \"u32\"),\n        (\"decompressed_size\", \"u64\"),\n        (\"null_byte\", \"u8\"),\n    ];\n\n    let mut lzma_hdr_info = LZMAHeader {\n        ..Default::default()\n    };\n\n    // Parse the lzma header\n    if let Ok(lzma_header) = common::parse(lzma_data, &lzma_structure, \"little\") {\n        // Make sure the expected NULL byte is NULL\n        if lzma_header[\"null_byte\"] == 0 {\n            // Sanity check the reported decompressed size\n            if lzma_header[\"decompressed_size\"] >= MIN_SUPPORTED_DECOMPRESSED_SIZE\n                && (lzma_header[\"decompressed_size\"] == LZMA_STREAM_SIZE\n                    || lzma_header[\"decompressed_size\"] <= MAX_SUPPORTED_DECOMPRESSED_SIZE)\n            {\n                lzma_hdr_info.properties = lzma_header[\"properties\"];\n                lzma_hdr_info.dictionary_size = lzma_header[\"dictionary_size\"];\n                lzma_hdr_info.decompressed_size = lzma_header[\"decompressed_size\"];\n\n                return Ok(lzma_hdr_info);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/lzop.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// LZO checksums are 4-bytes long\nconst LZO_CHECKSUM_SIZE: usize = 4;\n\n/// Struct to store LZOP file header info\n#[derive(Debug, Default, Clone)]\npub struct LZOPFileHeader {\n    pub header_size: usize,\n    pub block_checksum_present: bool,\n}\n\n/// Parse an LZOP file header\npub fn parse_lzop_file_header(lzop_data: &[u8]) -> Result<LZOPFileHeader, StructureError> {\n    // Max supported LZO version\n    const LZO_MAX_VERSION: usize = 0x1040;\n\n    const LZO_HEADER_SIZE_P1: usize = 21;\n    const LZO_HEADER_SIZE_P2: usize = 13;\n\n    const FILTER_SIZE: usize = 4;\n\n    const FLAG_FILTER: usize = 0x000_00800;\n    //const FLAG_CRC32_D: usize = 0x0000_0100;\n    const FLAG_CRC32_C: usize = 0x0000_0200;\n    //const FLAG_ADLER32_D: usize = 0x0000_0001;\n    const FLAG_ADLER32_C: usize = 0x0000_0002;\n\n    let lzo_structure_p1 = vec![\n        (\"magic_p1\", \"u8\"),\n        (\"magic_p2\", \"u64\"),\n        (\"version\", \"u16\"),\n        (\"lib_version\", \"u16\"),\n        (\"version_needed\", \"u16\"),\n        (\"method\", \"u8\"),\n        (\"level\", \"u8\"),\n        (\"flags\", \"u32\"),\n    ];\n\n    let lzo_structure_p2 = vec![\n        (\"mode\", \"u32\"),\n        (\"mtime\", \"u32\"),\n        (\"gmt_diff\", \"u32\"),\n        (\"file_name_length\", \"u8\"),\n    ];\n\n    let allowed_methods: Vec<usize> = vec![1, 2, 3];\n\n    let mut lzop_info = LZOPFileHeader {\n        ..Default::default()\n    };\n\n    // Parse the first part of the header\n    if let Ok(lzo_header_p1) = common::parse(lzop_data, &lzo_structure_p1, \"big\") {\n        // Sanity check the methods field\n        if allowed_methods.contains(&lzo_header_p1[\"method\"]) {\n            // Sanity check the header version numbers\n            if lzo_header_p1[\"version\"] <= LZO_MAX_VERSION\n                && lzo_header_p1[\"version\"] >= lzo_header_p1[\"version_needed\"]\n            {\n                // Unless the optional filter field is included, start of the second part of the header is at the end of the first\n                let mut header_p2_start: usize = LZO_HEADER_SIZE_P1;\n\n                // Next part of the header may or may not have an optional filter field\n                if (lzo_header_p1[\"flags\"] & FLAG_FILTER) != 0 {\n                    header_p2_start += FILTER_SIZE;\n                }\n\n                // Calculate the end of the second part of the header\n                let header_p2_end: usize = header_p2_start + LZO_HEADER_SIZE_P2;\n\n                if let Some(header_p2_data) = lzop_data.get(header_p2_start..header_p2_end) {\n                    // Parse the second part of the header\n                    if let Ok(lzo_header_p2) =\n                        common::parse(header_p2_data, &lzo_structure_p2, \"big\")\n                    {\n                        // Calculate the total header size; compressed data blocks will immediately follow\n                        lzop_info.header_size =\n                            header_p2_end + lzo_header_p2[\"file_name_length\"] + LZO_CHECKSUM_SIZE;\n\n                        // Check if block headers include an optional compressed data checksum field\n                        lzop_info.block_checksum_present =\n                            (lzo_header_p1[\"flags\"] & FLAG_ADLER32_C & FLAG_CRC32_C) != 0;\n\n                        // Sanity check on the calculated header size\n                        if lzop_info.header_size <= lzop_data.len() {\n                            return Ok(lzop_info);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Struct to store info on LZOP block headers\n#[derive(Debug, Default, Clone)]\npub struct LZOPBlockHeader {\n    pub header_size: usize,\n    pub compressed_size: usize,\n    pub uncompressed_size: usize,\n    pub checksum_size: usize,\n}\n\n/// Parse an LZO block header\npub fn parse_lzop_block_header(\n    lzo_data: &[u8],\n    compressed_checksum_present: bool,\n) -> Result<LZOPBlockHeader, StructureError> {\n    // Size constants\n    const BLOCK_HEADER_SIZE: usize = 12;\n    const MAX_UNCOMPRESSED_BLOCK_SIZE: usize = 64 * 1024 * 1024;\n\n    let block_structure = vec![\n        (\"uncompressed_size\", \"u32\"),\n        (\"compressed_size\", \"u32\"),\n        (\"uncompressed_checksum\", \"u32\"),\n    ];\n\n    // Parse the block header\n    if let Ok(block_header) = common::parse(lzo_data, &block_structure, \"big\") {\n        // Basic sanity check on the block header values\n        if block_header[\"compressed_size\"] != 0\n            && block_header[\"uncompressed_size\"] != 0\n            && block_header[\"uncompressed_checksum\"] != 0\n            && block_header[\"uncompressed_size\"] <= MAX_UNCOMPRESSED_BLOCK_SIZE\n        {\n            let mut block_hdr_info = LZOPBlockHeader {\n                ..Default::default()\n            };\n\n            block_hdr_info.header_size = BLOCK_HEADER_SIZE;\n            block_hdr_info.compressed_size = block_header[\"compressed_size\"];\n            block_hdr_info.uncompressed_size = block_header[\"uncompressed_size\"];\n\n            // Checksum field is optional\n            if compressed_checksum_present {\n                block_hdr_info.checksum_size = LZO_CHECKSUM_SIZE;\n            }\n\n            return Ok(block_hdr_info);\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parse an LZOP EOF marker, returns the size of the EOF marker (always 4 bytes)\npub fn parse_lzop_eof_marker(eof_data: &[u8]) -> Result<usize, StructureError> {\n    const EOF_MARKER: usize = 0;\n    const EOF_MARKER_SIZE: usize = 4;\n\n    let eof_structure = vec![(\"marker\", \"u32\")];\n\n    /*\n     * It is unclear, but observed, that LZOP files end with 0x00000000; this is assumed to be an EOF marker,\n     * as other similar compression file formats use that. This assumption could be incorrect.\n     */\n    if let Ok(eof_marker) = common::parse(eof_data, &eof_structure, \"big\") {\n        // Sanity check the EOF marker\n        if eof_marker[\"marker\"] == EOF_MARKER {\n            // Return the size of the EOF marker\n            return Ok(EOF_MARKER_SIZE);\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/matter_ota.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::common::{get_cstring, is_offset_safe};\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store Matter OTA header info\n#[derive(Debug, Default, Clone)]\npub struct MatterOTAHeader {\n    pub total_size: usize,\n    pub header_size: usize,\n    pub vendor_id: usize,\n    pub product_id: usize,\n    pub version: String,\n    pub payload_size: usize,\n    pub image_digest_type: usize,\n    pub image_digest: String,\n}\n\n#[derive(Debug)]\nenum Value {\n    Struct,\n    EndOfContainer,\n    Unsigned(usize),\n    String(String),\n    OctetString(Vec<u8>),\n}\n\n#[derive(Debug)]\nstruct Element {\n    tag: Option<usize>,\n    value: Value,\n}\n\n/// Parse a Matter OTA firmware header\npub fn parse_matter_ota_header(ota_data: &[u8]) -> Result<MatterOTAHeader, StructureError> {\n    let ota_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"total_size\", \"u64\"),\n        (\"header_size\", \"u32\"),\n    ];\n\n    if let Ok(ota_header) = common::parse(ota_data, &ota_structure, \"little\") {\n        let total_size: usize = ota_header[\"total_size\"];\n        let header_size: usize = ota_header[\"header_size\"];\n\n        // Header starts after the magic, total size and header size fields\n        let header_start = common::size(&ota_structure);\n        let header_end = header_start + header_size;\n        let header_data = ota_data\n            .get(header_start..header_end)\n            .ok_or(StructureError)?;\n\n        let header = parse_tlv_header(header_data)?;\n\n        let mut result = MatterOTAHeader {\n            total_size,\n            header_size,\n            ..Default::default()\n        };\n\n        for (key, value) in header.into_iter() {\n            match (key.as_ref(), value) {\n                (\"VendorID\", Value::Unsigned(vendor_id)) => result.vendor_id = vendor_id,\n                (\"ProductID\", Value::Unsigned(product_id)) => result.product_id = product_id,\n                (\"SoftwareVersionString\", Value::String(version)) => result.version = version,\n                (\"PayloadSize\", Value::Unsigned(payload_size)) => {\n                    result.payload_size = payload_size\n                }\n                (\"ImageDigestType\", Value::Unsigned(image_digest_type)) => {\n                    result.image_digest_type = image_digest_type\n                }\n                (\"ImageDigest\", Value::OctetString(image_digest)) => {\n                    let mut digest_string = String::new();\n                    for b in image_digest {\n                        digest_string.push_str(&format!(\"{b:02x}\"));\n                    }\n                    result.image_digest = digest_string;\n                }\n                // Ignore other fields\n                _ => {}\n            }\n        }\n\n        // Sanity check\n        if (result.payload_size + header_start + header_size) == total_size {\n            return Ok(result);\n        }\n    }\n    Err(StructureError)\n}\n\n/// Parse tlv element, return result and new offset\nfn parse_tlv_element(data: &[u8]) -> Result<(Element, usize), StructureError> {\n    let control_octet = data.first().ok_or(StructureError)?;\n\n    let element_type = control_octet & 0x1f;\n    let tag_control = control_octet >> 5;\n\n    // Lower 2 bits of the control octet determine the field width of integer types\n    // or the width of the length field for string types\n    let field_width_type = match element_type & 0x3 {\n        0 => \"u8\",\n        1 => \"u16\",\n        2 => \"u32\",\n        3 => \"u64\",\n        _ => return Err(StructureError),\n    };\n\n    // Parse numerical tag. Only supports anonymous fields and fields with a one byte tag\n    let (tag, field_offset) = match tag_control {\n        0 => (None, 1), // Anonymous field\n        1 => (Some(*data.get(1).ok_or(StructureError)? as usize), 2),\n        _ => return Err(StructureError),\n    };\n\n    let field_data = data.get(field_offset..).ok_or(StructureError)?;\n\n    match element_type {\n        0b1_0101 => Ok((\n            // Struct container\n            Element {\n                tag,\n                value: Value::Struct,\n            },\n            field_offset,\n        )),\n        0b1_1000 => Ok((\n            // End of container\n            Element {\n                tag,\n                value: Value::EndOfContainer,\n            },\n            field_offset,\n        )),\n        0b0_0100..=0b0_0111 => {\n            // Unsigned integer\n            let structure = &vec![(\"field\", field_width_type)];\n            let result = common::parse(field_data, structure, \"little\")?;\n            Ok((\n                Element {\n                    tag,\n                    value: Value::Unsigned(result[\"field\"]),\n                },\n                field_offset + common::size(structure),\n            ))\n        }\n        0b0_1100..=0b0_1111 => {\n            // UTF-8 String\n            let structure = &vec![(\"string_length\", field_width_type)];\n            let result = common::parse(field_data, structure, \"little\")?;\n            let string_length = result[\"string_length\"] as usize;\n            let string_data = field_data\n                .get(common::size(structure)..)\n                .ok_or(StructureError)?;\n            // The string buffer isn't null-terminated, so use the explicit length\n            let string = string_data\n                .get(..string_length)\n                .map(get_cstring)\n                .ok_or(StructureError)?;\n            Ok((\n                Element {\n                    tag,\n                    value: Value::String(string),\n                },\n                field_offset + common::size(structure) + string_length,\n            ))\n        }\n        0b1_0000..=0b1_0011 => {\n            // Octet string\n            let structure = &vec![(\"octet_string_length\", field_width_type)];\n            let result = common::parse(field_data, structure, \"little\")?;\n            let octet_string_length = result[\"octet_string_length\"] as usize;\n            let octet_string_data = field_data\n                .get(common::size(structure)..)\n                .ok_or(StructureError)?;\n            Ok((\n                Element {\n                    tag,\n                    value: Value::OctetString(\n                        octet_string_data\n                            .get(..octet_string_length)\n                            .ok_or(StructureError)?\n                            .to_vec(),\n                    ),\n                },\n                field_offset + common::size(structure) + octet_string_length,\n            ))\n        }\n        _ => Err(StructureError), // Other types are not implemented, but not necessary for header parsing\n    }\n}\n\nfn parse_tlv_header(data: &[u8]) -> Result<HashMap<String, Value>, StructureError> {\n    // Field names for the Matter OTA header indexed by the tag number\n    let fields = [\n        \"VendorID\",\n        \"ProductID\",\n        \"SoftwareVersion\",\n        \"SoftwareVersionString\",\n        \"PayloadSize\",\n        \"MinApplicableSoftwareVersion\",\n        \"MaxApplicableSoftwareVersion\",\n        \"ReleaseNotesURL\",\n        \"ImageDigestType\",\n        \"ImageDigest\",\n    ];\n    let mut header = HashMap::new();\n\n    let available_data: usize = data.len();\n\n    let mut last_tlv_offset: Option<usize> = None;\n    let mut next_tlv_offset: usize = 0;\n\n    while is_offset_safe(available_data, next_tlv_offset, last_tlv_offset) {\n        let (element, new_offset) = parse_tlv_element(&data[next_tlv_offset..])?;\n        last_tlv_offset = Some(next_tlv_offset);\n        next_tlv_offset += new_offset;\n        if let Some(tag) = element.tag {\n            let field_name = *fields.get(tag).ok_or(StructureError)?;\n            header.insert(field_name.to_string(), element.value);\n        }\n    }\n    Ok(header)\n}\n"
  },
  {
    "path": "src/structures/mbr.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Struct to store MBR partition info\n#[derive(Debug, Default, Clone)]\npub struct MBRPartition {\n    pub start: usize,\n    pub size: usize,\n    pub name: String,\n}\n\n/// Struct to store MBR info\n#[derive(Debug, Default, Clone)]\npub struct MBRHeader {\n    pub image_size: usize,\n    pub partitions: Vec<MBRPartition>,\n}\n\n/// Parse a Master Boot Record image\npub fn parse_mbr_image(mbr_data: &[u8]) -> Result<MBRHeader, StructureError> {\n    const BLOCK_SIZE: usize = 512;\n    const MIN_IMAGE_SIZE: usize = BLOCK_SIZE * 2;\n\n    const PARTITION_COUNT: usize = 4;\n    const PARTITION_TABLE_OFFSET: usize = 446;\n\n    let partition_entry_structure = vec![\n        (\"status\", \"u8\"),\n        (\"chs_start\", \"u24\"),\n        (\"os_type\", \"u8\"),\n        (\"chs_end\", \"u24\"),\n        (\"lba_start\", \"u32\"),\n        (\"lba_size\", \"u32\"),\n    ];\n\n    let known_os_types = HashMap::from([\n        (0x07, \"NTFS_IFS_HPFS_exFAT\"),\n        (0x0B, \"FAT32\"),\n        (0x0C, \"FAT32\"),\n        (0x43, \"Linux\"),\n        (0x4D, \"QNX Primary Volume\"),\n        (0x4E, \"QNX Secondary Volume\"),\n        (0x81, \"Minix\"),\n        (0x83, \"Linux\"),\n        (0x8E, \"Linux LVM\"),\n        (0x96, \"ISO-9660\"),\n        (0xB1, \"QNXv6 File System\"),\n        (0xB2, \"QNXv6 File System\"),\n        (0xB3, \"QNXv6 File System\"),\n        (0xEE, \"EFI GPT Protective\"),\n        (0xEF, \"EFI System Partition\"),\n    ]);\n\n    let allowed_status_values: Vec<usize> = vec![0, 0x80];\n    let partition_structure_size = common::size(&partition_entry_structure);\n\n    let partition_table_start: usize = PARTITION_TABLE_OFFSET;\n    let partition_table_end: usize =\n        partition_table_start + (partition_structure_size * PARTITION_COUNT);\n\n    let mut mbr_header = MBRHeader {\n        ..Default::default()\n    };\n\n    // Get the partition table raw bytes\n    if let Some(partition_table) = mbr_data.get(partition_table_start..partition_table_end) {\n        // Parse each partition table entry\n        for i in 0..PARTITION_COUNT {\n            // Offset in the partition table for this entry\n            let partition_entry_start: usize = i * partition_structure_size;\n\n            // Parse this partition table entry\n            match common::parse(\n                &partition_table[partition_entry_start..],\n                &partition_entry_structure,\n                \"little\",\n            ) {\n                Err(_) => {\n                    return Err(StructureError);\n                }\n                Ok(partition_entry) => {\n                    // OS type of zero or LBA size of 0 can be ignored\n                    if partition_entry[\"os_type\"] != 0 || partition_entry[\"lba_size\"] != 0 {\n                        // Validate the reported MBR status value\n                        if allowed_status_values.contains(&partition_entry[\"status\"]) {\n                            // Default to unknown partition type\n                            let mut this_partition_name: &str = \"Unknown\";\n\n                            // If partition type is known, provide a descriptive name\n                            if known_os_types.contains_key(&partition_entry[\"os_type\"]) {\n                                this_partition_name = known_os_types[&partition_entry[\"os_type\"]];\n                            }\n\n                            // Create an MBRPartition structure for this entry\n                            let this_partition = MBRPartition {\n                                start: partition_entry[\"lba_start\"] * BLOCK_SIZE,\n                                size: partition_entry[\"lba_size\"] * BLOCK_SIZE,\n                                name: this_partition_name.to_string(),\n                            };\n\n                            // Calculate where this partition ends\n                            let this_partition_end_offset =\n                                this_partition.start + this_partition.size;\n\n                            // Some valid MBRs have partitions that start/end out of bounds WRT the disk image.\n                            // Not sure why? At any rate, don't include them in the reported partitions.\n                            if this_partition_end_offset <= mbr_data.len() {\n                                // Don't report the partition where the MBR header resides\n                                if this_partition.start != 0 {\n                                    // Add it to the list of partitions\n                                    mbr_header.partitions.push(this_partition.clone());\n                                }\n\n                                // Image size is the end of the farthest away partition\n                                if this_partition_end_offset > mbr_header.image_size {\n                                    mbr_header.image_size = this_partition_end_offset;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // There should be at least one valid partition\n        if !mbr_header.partitions.is_empty() {\n            // Total size should be greater than minimum size\n            if mbr_header.image_size > MIN_IMAGE_SIZE {\n                return Ok(mbr_header);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/mh01.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store MH01 header info\n#[derive(Debug, Default, Clone)]\npub struct MH01Header {\n    pub iv: String,\n    pub iv_offset: usize,\n    pub iv_size: usize,\n    pub signature_offset: usize,\n    pub signature_size: usize,\n    pub encrypted_data_offset: usize,\n    pub encrypted_data_size: usize,\n    pub total_size: usize,\n}\n\n/// Parses an MH01 header\npub fn parse_mh01_header(mh01_data: &[u8]) -> Result<MH01Header, StructureError> {\n    const HEADER_SIZE: usize = 16;\n\n    // This structure is actually two MH01 headers, each header is HEADER_SIZE bytes long.\n    // The first header describes the offset and size of the firmware signature.\n    // The second header describes the offset and size of the encrypted firmware image.\n    // The OpenSSL IV is stored as an ASCII hex string between the second header and the encrypted firmware image.\n    let mh01_structure = vec![\n        (\"magic1\", \"u32\"),\n        (\"signature_offset\", \"u32\"),\n        (\"signature_size\", \"u32\"),\n        (\"unknown1\", \"u32\"),\n        (\"magic2\", \"u32\"),\n        (\"iv_size\", \"u32\"),\n        (\"encrypted_data_size\", \"u32\"),\n        (\"unknown2\", \"u32\"),\n        // IV string of length iv_size immediately follows\n    ];\n\n    let mut result = MH01Header {\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(header) = common::parse(mh01_data, &mh01_structure, \"little\") {\n        // Make sure the expected magic bytes match\n        if header[\"magic1\"] == header[\"magic2\"] {\n            // IV size is specified in the header and immediately follows the header\n            result.iv_size = header[\"iv_size\"];\n            result.iv_offset = common::size(&mh01_structure);\n\n            // The encrypted firmware image immediately follows the IV\n            result.encrypted_data_size = header[\"encrypted_data_size\"];\n            result.encrypted_data_offset = result.iv_offset + result.iv_size;\n\n            // The signature should immediately follow the encrypted firmware image\n            result.signature_size = header[\"signature_size\"];\n            result.signature_offset = HEADER_SIZE + header[\"signature_offset\"];\n\n            // Calculate the start and end bytes of the IV (ASCII hex)\n            let iv_bytes_start = result.iv_offset;\n            let iv_bytes_end = result.encrypted_data_offset;\n\n            // Get the payload hash string\n            if let Some(iv_bytes) = mh01_data.get(iv_bytes_start..iv_bytes_end) {\n                let iv_string = get_cstring(iv_bytes);\n\n                // Make sure we got a string of the expected length\n                if iv_string.len() == result.iv_size {\n                    result.iv = iv_string.trim().to_string();\n                    result.total_size = result.signature_offset + result.signature_size;\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/ntfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store NTFS info\n#[derive(Debug, Default, Clone)]\npub struct NTFSPartition {\n    pub sector_size: usize,\n    pub sector_count: usize,\n}\n\n/// Parses an NTFS partition header\npub fn parse_ntfs_header(ntfs_data: &[u8]) -> Result<NTFSPartition, StructureError> {\n    // https://en.wikipedia.org/wiki/NTFS\n    let ntfs_structure = vec![\n        (\"opcodes\", \"u24\"),\n        (\"magic\", \"u64\"),\n        (\"bytes_per_sector\", \"u16\"),\n        (\"sectors_per_cluster\", \"u8\"),\n        (\"unused1\", \"u16\"),\n        (\"unused2\", \"u24\"),\n        (\"unused3\", \"u16\"),\n        (\"media_type\", \"u8\"),\n        (\"unused4\", \"u16\"),\n        (\"sectors_per_track\", \"u16\"),\n        (\"head_count\", \"u16\"),\n        (\"hidden_sector_count\", \"u32\"),\n        (\"unused5\", \"u32\"),\n        (\"unknown\", \"u32\"),\n        (\"sector_count\", \"u64\"),\n    ];\n\n    // Parse the NTFS partition header\n    if let Ok(ntfs_header) = common::parse(ntfs_data, &ntfs_structure, \"little\") {\n        // Sanity check to make sure the unused fields are not used\n        if ntfs_header[\"unused1\"] == 0\n            && ntfs_header[\"unused2\"] == 0\n            && ntfs_header[\"unused3\"] == 0\n            && ntfs_header[\"unused4\"] == 0\n            && ntfs_header[\"unused5\"] == 0\n        {\n            return Ok(NTFSPartition {\n                sector_count: ntfs_header[\"sector_count\"],\n                sector_size: ntfs_header[\"bytes_per_sector\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/openssl.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store info on an OpenSSL crypto header\npub struct OpenSSLCryptHeader {\n    pub salt: usize,\n}\n\n/// Parse an OpenSSl crypto header\npub fn parse_openssl_crypt_header(ssl_data: &[u8]) -> Result<OpenSSLCryptHeader, StructureError> {\n    let ssl_structure = vec![(\"magic\", \"u64\"), (\"salt\", \"u64\")];\n\n    if let Ok(ssl_header) = common::parse(ssl_data, &ssl_structure, \"big\") {\n        if ssl_header[\"salt\"] != 0 {\n            return Ok(OpenSSLCryptHeader {\n                salt: ssl_header[\"salt\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/packimg.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store PackIMG header info\npub struct PackIMGHeader {\n    pub header_size: usize,\n    pub data_size: usize,\n}\n\n/// Parse a PackIMG header\npub fn parse_packimg_header(packimg_data: &[u8]) -> Result<PackIMGHeader, StructureError> {\n    // Fixed size header\n    const PACKIMG_HEADER_SIZE: usize = 32;\n\n    let packimg_structure = vec![\n        (\"magic_p1\", \"u32\"),\n        (\"magic_p2\", \"u32\"),\n        (\"magic_p3\", \"u32\"),\n        (\"padding1\", \"u32\"),\n        (\"data_size\", \"u32\"),\n    ];\n\n    // Parse the packimg header\n    if let Ok(packimg_header) = common::parse(packimg_data, &packimg_structure, \"little\") {\n        return Ok(PackIMGHeader {\n            header_size: PACKIMG_HEADER_SIZE,\n            data_size: packimg_header[\"data_size\"],\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/pcap.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Storage struct for Pcap block info\n#[derive(Debug, Clone, Default)]\npub struct PcapBlock {\n    pub block_type: usize,\n    pub block_size: usize,\n}\n\n/// Parse a Pcap-ng block\npub fn parse_pcapng_block(\n    block_data: &[u8],\n    endianness: &str,\n) -> Result<PcapBlock, StructureError> {\n    // Reserved bit in block type field\n    const BLOCK_TYPE_RESERVED_MASK: usize = 0x80000000;\n\n    let block_header_structure = vec![(\"block_type\", \"u32\"), (\"block_size\", \"u32\")];\n\n    let block_footer_structure = vec![(\"block_size\", \"u32\")];\n\n    let mut result = PcapBlock {\n        ..Default::default()\n    };\n\n    let footer_size = common::size(&block_footer_structure);\n\n    // Parse the block header\n    if let Ok(block_header) = common::parse(block_data, &block_header_structure, endianness) {\n        // Populate the block type and size values\n        result.block_type = block_header[\"block_type\"];\n        result.block_size = block_header[\"block_size\"];\n\n        // Make sure the reserved bit of the block type is not set\n        if (result.block_type & BLOCK_TYPE_RESERVED_MASK) == 0 {\n            // Calculate the block footer offsets\n            let block_footer_start = result.block_size - footer_size;\n            let block_footer_end = block_footer_start + footer_size;\n\n            // Validate that the block size in the block footer matches the block size in the block header\n            if let Some(block_footer_data) = block_data.get(block_footer_start..block_footer_end) {\n                if let Ok(block_footer) =\n                    common::parse(block_footer_data, &block_footer_structure, endianness)\n                {\n                    if block_footer[\"block_size\"] == result.block_size {\n                        return Ok(result);\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n#[derive(Debug, Default, Clone)]\npub struct PcapSectionBlock {\n    pub block_size: usize,\n    pub endianness: String,\n}\n\n/// Parse a Pcap-ng section block\npub fn parse_pcapng_section_block(block_data: &[u8]) -> Result<PcapSectionBlock, StructureError> {\n    // Section header block type (same value, regardless of endianness)\n    const SECTION_HEADER_BLOCK_TYPE: usize = 0x0A0D0D0A;\n\n    let section_header_structure = vec![\n        (\"block_type\", \"u32\"),\n        (\"block_size\", \"u32\"),\n        (\"endian_magic\", \"u32\"),\n        (\"major_version\", \"u16\"),\n        (\"minor_version\", \"u16\"),\n        (\"section_length\", \"u32\"),\n    ];\n\n    let endian_magics: HashMap<usize, &str> =\n        HashMap::from([(0x1A2B3C4D, \"little\"), (0x4D3C2B1A, \"big\")]);\n\n    let mut result = PcapSectionBlock {\n        ..Default::default()\n    };\n\n    // Parse the section header structure; endianess doesn't matter (yet)\n    if let Ok(section_header) = common::parse(block_data, &section_header_structure, \"little\") {\n        // Determine the endianness based on the endian magic bytes\n        if endian_magics.contains_key(&section_header[\"endian_magic\"]) {\n            result.endianness = endian_magics[&section_header[\"endian_magic\"]].to_string();\n\n            // Parse the section header block as a generic block to ensure it is valid\n            if let Ok(block_header) = parse_pcapng_block(block_data, &result.endianness) {\n                // Make sure the section header block type is the expected value\n                if block_header.block_type == SECTION_HEADER_BLOCK_TYPE {\n                    result.block_size = block_header.block_size;\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/pchrom.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store PCHROM image info\n#[derive(Debug, Default, Clone)]\npub struct PCHRomHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parse a PCHROM header\npub fn parse_pchrom_header(pch_data: &[u8]) -> Result<PCHRomHeader, StructureError> {\n    // Structure is a fixed size, at a fixed offset from the beginning of the PCHROM image\n    const HEADER_STRUCTURE_SIZE: usize = 8;\n    const HEADER_STRUCTURE_OFFSET: usize = 16;\n\n    // All \"expected\" values are derived from the Intel PCH Programming Manual\n    const EXPECTED_FCBA: usize = 3;\n    const EXPECTED_FRBA: usize = 4;\n\n    let expected_nc_values: Vec<usize> = vec![0, 1];\n\n    let pch_rom_header_structure = vec![\n        (\"flmagic\", \"u32\"),\n        (\"flmap0_fcba\", \"u8\"),\n        (\"flmap0_nc\", \"u8\"),\n        (\"flmap0_frba_nr\", \"u16\"),\n    ];\n\n    // Calculate the header structure start and end offsets\n    let struct_start: usize = HEADER_STRUCTURE_OFFSET;\n    let struct_end: usize = struct_start + HEADER_STRUCTURE_SIZE;\n\n    if let Some(pch_structure_data) = pch_data.get(struct_start..struct_end) {\n        // Parse the header structure\n        if let Ok(pch_header) =\n            common::parse(pch_structure_data, &pch_rom_header_structure, \"little\")\n        {\n            // Sanity check the expected header values\n            if pch_header[\"flmap0_fcba\"] == EXPECTED_FCBA\n                && pch_header[\"flmap0_frba_nr\"] == EXPECTED_FRBA\n                && expected_nc_values.contains(&pch_header[\"flmap0_nc\"])\n            {\n                // Parse the flash rom region entries to determine the total image size\n                if let Ok(pch_regions_size) =\n                    get_pch_regions_size(pch_data, 0, pch_header[\"flmap0_fcba\"])\n                {\n                    return Ok(PCHRomHeader {\n                        header_size: HEADER_STRUCTURE_OFFSET,\n                        data_size: pch_regions_size,\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Determine the total size of PCHROM regions\nfn get_pch_regions_size(\n    pch_data: &[u8],\n    offset: usize,\n    fcba: usize,\n) -> Result<usize, StructureError> {\n    // There are 5 defined flash regions: Descriptor, BIOS, ME, GBE, PDATA\n    const FLASH_REGION_COUNT: usize = 5;\n\n    // Each entry is a 32-bit value describing the region\n    const FLASH_REGION_ENTRY_SIZE: usize = 4;\n\n    // There is a 32-bit entry for each possible region in the PCH image\n    let region_entry_structure = vec![(\"region_value\", \"u32\")];\n\n    let mut image_size: usize = 0;\n\n    // The base address of the flash regions is encoded into 8 bits of the FCBA header field, like so\n    let flash_region_base_address: usize = ((fcba >> 16) & 0xFF) << 4;\n\n    // Region entries are 32-bit values stored seqeuntially starting at the flash region base address\n    for i in 0..FLASH_REGION_COUNT {\n        // Get the offset of the next region's 32-bit entry\n        let region_entry_start = offset + flash_region_base_address + (i * FLASH_REGION_ENTRY_SIZE);\n        let region_entry_end = region_entry_start + FLASH_REGION_ENTRY_SIZE;\n\n        // Get the next region's 32-bit value, in raw bytes\n        match pch_data.get(region_entry_start..region_entry_end) {\n            None => {\n                return Err(StructureError);\n            }\n            Some(pch_region_data) => {\n                // Parse the 32-bit entry value for this region\n                if let Ok(region_entry) =\n                    common::parse(pch_region_data, &region_entry_structure, \"little\")\n                {\n                    let region_value = region_entry[\"region_value\"];\n\n                    // The base (starting offset) and limit (ending offset) of the region is encoded into the 32-bit entry value\n                    let region_base = (region_value & 0x1FFF) << 12;\n                    let region_limit = (((region_value & 0x1FFF0000) >> 4) | 0xFFFF) + 1;\n\n                    // Size can be inferred from the base and limit values\n                    let region_size = region_limit - region_base;\n\n                    // If size is 0, this region is not used in this image\n                    if region_size > 0 && region_limit > image_size {\n                        image_size = region_limit;\n                    }\n                }\n            }\n        }\n    }\n\n    if image_size > 0 {\n        return Ok(image_size);\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/pe.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Stores info about the PE file\npub struct PEHeader {\n    pub machine: String,\n}\n\n/// Partially parse a PE header\npub fn parse_pe_header(pe_data: &[u8]) -> Result<PEHeader, StructureError> {\n    const PE_MAGIC: usize = 0x00004550;\n\n    let dos_structure = vec![\n        (\"e_magic\", \"u16\"),    // \"MZ\"\n        (\"e_cblp\", \"u16\"),     // Bytes on last page of file\n        (\"e_cp\", \"u16\"),       // Pages in file\n        (\"e_crlc\", \"u16\"),     // Relocations\n        (\"e_cparhdr\", \"u16\"),  // Header size, in paragraphs\n        (\"e_minalloc\", \"u16\"), // Min extra paragraphs needed\n        (\"e_maxalloc\", \"u16\"), // Max extra paragraphs needed\n        (\"e_ss\", \"u16\"),       // Initial relative SS value\n        (\"e_sp\", \"u16\"),       // Initial SP value\n        (\"e_csum\", \"u16\"),     // Checksum\n        (\"e_ip\", \"u16\"),       // Initial IP value\n        (\"e_cs\", \"u16\"),       // Initial relative CS value\n        (\"e_lfarlc\", \"u16\"),   // File address of relocation table\n        (\"e_ovno\", \"u16\"),     // Overlay number\n        (\"e_res_1\", \"u16\"),\n        (\"e_res_2\", \"u16\"),\n        (\"e_res_3\", \"u16\"),\n        (\"e_res_4\", \"u16\"),\n        (\"e_oemid\", \"u16\"),   // OEM identifier\n        (\"e_oeminfo\", \"u16\"), // OEM specific information\n        (\"e_res_5\", \"u16\"),\n        (\"e_res_6\", \"u16\"),\n        (\"e_res_7\", \"u16\"),\n        (\"e_res_8\", \"u16\"),\n        (\"e_res_9\", \"u16\"),\n        (\"e_res_10\", \"u16\"),\n        (\"e_res_11\", \"u16\"),\n        (\"e_res_12\", \"u16\"),\n        (\"e_res_13\", \"u16\"),\n        (\"e_res_14\", \"u16\"),\n        (\"e_lfanew\", \"u32\"), // Offset to the PE header\n    ];\n\n    let pe_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"machine\", \"u16\"),\n        (\"number_of_sections\", \"u16\"),\n        (\"timestamp\", \"u32\"),\n        (\"symbol_table_ptr\", \"u32\"),\n        (\"number_of_symbols\", \"u32\"),\n        (\"optional_header_size\", \"u16\"),\n        (\"characteristics\", \"u16\"),\n    ];\n\n    let known_machine_types: HashMap<usize, &str> = HashMap::from([\n        (0, \"Unknown\"),\n        (0x184, \"Alpha32\"),\n        (0x284, \"Alpha64\"),\n        (0x1D3, \"Matsushita AM33\"),\n        (0x8664, \"Intel x86-64\"),\n        (0x1C0, \"ARM\"),\n        (0xAA64, \"ARM-64\"),\n        (0x1C4, \"ARM Thumb2\"),\n        (0xEBC, \"EFI\"),\n        (0x14C, \"Intel x86\"),\n        (0x200, \"Intel Itanium\"),\n        (0x6232, \"LoongArch 32-bit\"),\n        (0x6264, \"LoongArch 64-bit\"),\n        (0x9041, \"Mitsubishi M32R\"),\n        (0x266, \"MIPS16\"),\n        (0x366, \"MIPS with FPU\"),\n        (0x466, \"MIPS16 with FPU\"),\n        (0x1F0, \"PowerPC\"),\n        (0x1F1, \"PowerPC with FPU\"),\n        (0x5032, \"RISC-V 32-bit\"),\n        (0x5064, \"RISC-V 64-bit\"),\n        (0x5128, \"RISC-V 128-bit\"),\n        (0x1A2, \"Hitachi SH3\"),\n        (0x1A3, \"Hitachi SH3 DSP\"),\n        (0x1A6, \"Hitachi SH4\"),\n        (0x1A8, \"Hitachi SH5\"),\n        (0x1C2, \"Thumb\"),\n        (0x169, \"MIPS WCEv2\"),\n    ]);\n\n    // Size of PE header structure\n    let pe_header_size = common::size(&pe_structure);\n\n    // Parse the DOS header\n    if let Ok(dos_header) = common::parse(pe_data, &dos_structure, \"little\") {\n        // Sanity check the reserved header fields; they should all be 0\n        if dos_header[\"e_res_1\"] == 0\n            && dos_header[\"e_res_2\"] == 0\n            && dos_header[\"e_res_3\"] == 0\n            && dos_header[\"e_res_4\"] == 0\n            && dos_header[\"e_res_5\"] == 0\n            && dos_header[\"e_res_6\"] == 0\n            && dos_header[\"e_res_7\"] == 0\n            && dos_header[\"e_res_8\"] == 0\n            && dos_header[\"e_res_9\"] == 0\n            && dos_header[\"e_res_10\"] == 0\n            && dos_header[\"e_res_11\"] == 0\n            && dos_header[\"e_res_12\"] == 0\n            && dos_header[\"e_res_13\"] == 0\n            && dos_header[\"e_res_14\"] == 0\n        {\n            // Start and end offsets of the PE header\n            let pe_header_start: usize = dos_header[\"e_lfanew\"];\n            let pe_header_end: usize = pe_header_start + pe_header_size;\n\n            // Sanity check the PE header offsets\n            if let Some(pe_header_data) = pe_data.get(pe_header_start..pe_header_end) {\n                // Parse the PE header\n                if let Ok(pe_header) = common::parse(pe_header_data, &pe_structure, \"little\") {\n                    // Check the PE magic bytes\n                    if pe_header[\"magic\"] == PE_MAGIC {\n                        // Check the reported machine type\n                        if known_machine_types.contains_key(&pe_header[\"machine\"]) {\n                            return Ok(PEHeader {\n                                machine: known_machine_types[&pe_header[\"machine\"]].to_string(),\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/png.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info on a PNG chunk header\npub struct PNGChunkHeader {\n    pub total_size: usize,\n    pub is_last_chunk: bool,\n}\n\n/// Parse a PNG chunk header\npub fn parse_png_chunk_header(chunk_data: &[u8]) -> Result<PNGChunkHeader, StructureError> {\n    // All PNG chunks are followed by a 4-byte CRC\n    const CRC_SIZE: usize = 4;\n\n    // The \"IEND\" chunk is the last chunk in the PNG\n    const IEND_CHUNK_TYPE: usize = 0x49454E44;\n\n    let png_chunk_structure = vec![(\"length\", \"u32\"), (\"type\", \"u32\")];\n\n    let chunk_structure_size: usize = common::size(&png_chunk_structure);\n\n    // Parse the chunk header\n    if let Ok(chunk_header) = common::parse(chunk_data, &png_chunk_structure, \"big\") {\n        return Ok(PNGChunkHeader {\n            is_last_chunk: chunk_header[\"type\"] == IEND_CHUNK_TYPE,\n            total_size: chunk_structure_size + chunk_header[\"length\"] + CRC_SIZE,\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/qcow.rs",
    "content": "use crate::structures::common;\nuse crate::structures::common::StructureError;\nuse std::collections::HashMap;\n\n#[derive(Debug, Default, Clone)]\npub struct QcowHeader {\n    pub version: u8,\n    pub storage_media_size: u64,\n    pub cluster_block_bits: u8,\n    pub encryption_method: String,\n}\n\npub fn parse_qcow_header(qcow_data: &[u8]) -> Result<QcowHeader, StructureError> {\n    let qcow_basehdr_structure = vec![(\"magic\", \"u32\"), (\"version\", \"u32\")];\n    let qcow_header_v1_structure = vec![\n        (\"backing_filename_offset\", \"u64\"),\n        (\"backing_filename_size\", \"u32\"),\n        (\"modification_timestamp\", \"u32\"),\n        (\"storage_media_size\", \"u64\"),\n        (\"cluster_block_bits\", \"u8\"),\n        (\"level2_table_bits\", \"u8\"),\n        (\"reserved1\", \"u16\"),\n        (\"encryption_method\", \"u32\"),\n        (\"level1_table_offset\", \"u64\"),\n    ];\n    let qcow_header_v2_structure = vec![\n        (\"backing_filename_offset\", \"u64\"),\n        (\"backing_filename_size\", \"u32\"),\n        (\"cluster_block_bits\", \"u32\"),\n        (\"storage_media_size\", \"u64\"),\n        (\"encryption_method\", \"u32\"),\n        (\"level1_table_refs\", \"u32\"),\n        (\"level1_table_offset\", \"u64\"),\n        (\"refcount_table_offset\", \"u64\"),\n        (\"refcount_table_clusters\", \"u32\"),\n        (\"snapshot_count\", \"u32\"),\n        (\"snapshot_offset\", \"u64\"),\n    ];\n    let qcow_header_v3_structure = vec![\n        (\"backing_filename_offset\", \"u64\"),\n        (\"backing_filename_size\", \"u32\"),\n        (\"cluster_block_bits\", \"u32\"),\n        (\"storage_media_size\", \"u64\"),\n        (\"encryption_method\", \"u32\"),\n        (\"level1_table_refs\", \"u32\"),\n        (\"level1_table_offset\", \"u64\"),\n        (\"refcount_table_offset\", \"u64\"),\n        (\"refcount_table_clusters\", \"u32\"),\n        (\"snapshot_count\", \"u32\"),\n        (\"snapshot_offset\", \"u64\"),\n        (\"incompatible_feature_flags\", \"u64\"),\n        (\"compatible_feature_flags\", \"u64\"),\n        (\"autoclear_feature_flags\", \"u64\"),\n        (\"refcount_order\", \"u32\"),\n        (\"file_hdr_size\", \"u32\"), // 104 or 112\n    ];\n\n    let encryption_methods = HashMap::from([(0, \"None\"), (1, \"AES128-CBC\"), (2, \"LUKS\")]);\n\n    if let Ok(qcow_base_header) = common::parse(qcow_data, &qcow_basehdr_structure, \"big\") {\n        let qcow_version = qcow_base_header[\"version\"];\n        let qcow_data = qcow_data.get(8..).ok_or(StructureError)?;\n        let qcow_header = match qcow_version {\n            1 => common::parse(qcow_data, &qcow_header_v1_structure, \"big\"),\n            2 => common::parse(qcow_data, &qcow_header_v2_structure, \"big\"),\n            3 => common::parse(qcow_data, &qcow_header_v3_structure, \"big\"),\n            _ => Err(StructureError),\n        }?;\n\n        let encryption_method = encryption_methods\n            .get(&qcow_header[\"encryption_method\"])\n            .ok_or(StructureError)?\n            .to_string();\n\n        let cluster_block_bits = *qcow_header\n            .get(\"cluster_block_bits\")\n            .filter(|&&bits| (9..=21).contains(&bits))\n            .ok_or(StructureError)?;\n\n        // sanity check: existing offsets need to be aligned to cluster boundary\n        if let Some(offset) = qcow_header.get(\"level1_table_offset\") {\n            if offset % (1 << cluster_block_bits) != 0 {\n                return Err(StructureError);\n            }\n        }\n        if let Some(offset) = qcow_header.get(\"refcount_table_offset\") {\n            if offset % (1 << cluster_block_bits) != 0 {\n                return Err(StructureError);\n            }\n        }\n        if let Some(offset) = qcow_header.get(\"snapshot_offset\") {\n            if offset % (1 << cluster_block_bits) != 0 {\n                return Err(StructureError);\n            }\n        }\n\n        return Ok(QcowHeader {\n            version: qcow_version as u8,\n            storage_media_size: qcow_header[\"storage_media_size\"] as u64,\n            cluster_block_bits: cluster_block_bits as u8,\n            encryption_method,\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/qnx.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info on a QNX IFS header\npub struct IFSHeader {\n    pub total_size: usize,\n}\n\n/// Parse a QNX IFS header\npub fn parse_ifs_header(ifs_data: &[u8]) -> Result<IFSHeader, StructureError> {\n    // https://github.com/askac/dumpifs/blob/master/sys/startup.h\n    let ifs_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"version\", \"u16\"),\n        (\"flags1\", \"u8\"),\n        (\"flags2\", \"u8\"),\n        (\"header_size\", \"u16\"),\n        (\"machine\", \"u16\"),\n        (\"startup_vaddr\", \"u32\"),\n        (\"paddr_bias\", \"u32\"),\n        (\"image_paddr\", \"u32\"),\n        (\"ram_paddr\", \"u32\"),\n        (\"ram_size\", \"u32\"),\n        (\"startup_size\", \"u32\"),\n        (\"stored_size\", \"u32\"),\n        (\"imagefs_paddr\", \"u32\"),\n        (\"imagefs_size\", \"u32\"),\n        (\"preboot_size\", \"u16\"),\n        (\"zero_0\", \"u16\"),\n        (\"zero_1\", \"u32\"),\n        (\"zero_2\", \"u32\"),\n        (\"zero_3\", \"u32\"),\n    ];\n\n    // Parse the IFS header\n    if let Ok(ifs_header) = common::parse(ifs_data, &ifs_structure, \"little\") {\n        // The flags2 field is unused and should be 0\n        if ifs_header[\"flags2\"] == 0 {\n            // Verify that all the zero fields are, in fact, zero\n            if ifs_header[\"zero_0\"] == 0\n                && ifs_header[\"zero_1\"] == 0\n                && ifs_header[\"zero_2\"] == 0\n                && ifs_header[\"zero_3\"] == 0\n            {\n                return Ok(IFSHeader {\n                    total_size: ifs_header[\"stored_size\"],\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/rar.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Stores info on a RAR archive\n#[derive(Debug, Default, Clone)]\npub struct RarArchiveHeader {\n    pub version: usize,\n}\n\n/// Parse a RAR archive header\npub fn parse_rar_archive_header(rar_data: &[u8]) -> Result<RarArchiveHeader, StructureError> {\n    let archive_header_structure =\n        vec![(\"magic_p1\", \"u32\"), (\"magic_p2\", \"u16\"), (\"version\", \"u8\")];\n\n    // Version field of 0 indicates RARv4; version field of 1 indicates RARv5\n    let version_map: HashMap<usize, usize> = HashMap::from([(0, 4), (1, 5)]);\n\n    // Parse the header\n    if let Ok(archive_header) = common::parse(rar_data, &archive_header_structure, \"little\") {\n        // Make sure the version number is one of the known versions\n        if version_map.contains_key(&archive_header[\"version\"]) {\n            return Ok(RarArchiveHeader {\n                version: version_map[&archive_header[\"version\"]],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/riff.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store info from a RIFF header\npub struct RIFFHeader {\n    pub size: usize,\n    pub chunk_type: String,\n}\n\n/// Parse a RIFF image header\npub fn parse_riff_header(riff_data: &[u8]) -> Result<RIFFHeader, StructureError> {\n    const MAGIC: usize = 0x46464952;\n\n    const CHUNK_TYPE_START: usize = 8;\n    const CHUNK_TYPE_END: usize = 12;\n\n    const FILE_SIZE_OFFSET: usize = 8;\n\n    let riff_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"file_size\", \"u32\"),\n        (\"chunk_type\", \"u32\"),\n    ];\n\n    // Parse the riff header\n    if let Ok(riff_header) = common::parse(riff_data, &riff_structure, \"little\") {\n        // Sanity check expected magic bytes\n        if riff_header[\"magic\"] == MAGIC {\n            // Get the RIFF type string (e.g., \"WAVE\")\n            if let Ok(type_string) =\n                String::from_utf8(riff_data[CHUNK_TYPE_START..CHUNK_TYPE_END].to_vec())\n            {\n                return Ok(RIFFHeader {\n                    size: riff_header[\"file_size\"] + FILE_SIZE_OFFSET,\n                    chunk_type: type_string.trim().to_string(),\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/romfs.rs",
    "content": "use crate::common::get_cstring;\nuse crate::structures::common::{self, StructureError};\n\n/// Stores RomFS header info\n#[derive(Default, Debug, Clone)]\npub struct RomFSHeader {\n    pub image_size: usize,\n    pub header_size: usize,\n    pub volume_name: String,\n}\n\n/// Parse a RomFS header\npub fn parse_romfs_header(romfs_data: &[u8]) -> Result<RomFSHeader, StructureError> {\n    // Maximum amount of data that the RomFS CRC is calculated over\n    const MAX_HEADER_CRC_DATA_LEN: usize = 512;\n\n    let header_structure = vec![(\"magic\", \"u64\"), (\"image_size\", \"u32\"), (\"checksum\", \"u32\")];\n\n    // Get the size of the defined header structure\n    let header_size = common::size(&header_structure);\n\n    // Parse the header structure\n    if let Ok(header) = common::parse(romfs_data, &header_structure, \"big\") {\n        // Sanity check the reported image size\n        if header[\"image_size\"] > header_size {\n            // The volume name is a NULL-terminated string that immediately follows the RomFS header\n            if let Some(volume_name_bytes) = romfs_data.get(header_size..) {\n                let volume_name = get_cstring(volume_name_bytes);\n\n                let mut crc_data_len: usize = MAX_HEADER_CRC_DATA_LEN;\n\n                if header[\"image_size\"] < crc_data_len {\n                    crc_data_len = header[\"image_size\"];\n                }\n\n                // Validate the header CRC\n                if let Some(crc_data) = romfs_data.get(0..crc_data_len) {\n                    if romfs_crc_valid(crc_data) {\n                        return Ok(RomFSHeader {\n                            image_size: header[\"image_size\"],\n                            volume_name: volume_name.clone(),\n                            // Volume name has a NULL terminator and is padded to a 16 byte boundary alignment\n                            header_size: header_size + romfs_align(volume_name.len() + 1),\n                        });\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Struct to store info on a RomFS file entry\n#[derive(Debug, Default, Clone)]\npub struct RomFSFileHeader {\n    pub info: usize,\n    pub size: usize,\n    pub name: String,\n    pub checksum: usize,\n    /// Offset to the start of the file data, *relative to the beginning of this header*\n    pub data_offset: usize,\n    pub file_type: usize,\n    pub executable: bool,\n    pub symlink: bool,\n    pub directory: bool,\n    pub regular: bool,\n    pub block_device: bool,\n    pub character_device: bool,\n    pub fifo: bool,\n    pub socket: bool,\n    /// Offset to the next file header, *relative to the beginning of the RomFS image*\n    pub next_header_offset: usize,\n}\n\n/// Parse a RomFS file entry\npub fn parse_romfs_file_entry(romfs_data: &[u8]) -> Result<RomFSFileHeader, StructureError> {\n    // Bit masks\n    const FILE_TYPE_MASK: usize = 0b0111;\n    const FILE_EXEC_MASK: usize = 0b1000;\n    const NEXT_OFFSET_MASK: usize = 0b11111111_11111111_11111111_11110000;\n\n    // We only support extraction of these file types\n    const ROMFS_DIRECTORY: usize = 1;\n    const ROMFS_REGULAR_FILE: usize = 2;\n    const ROMFS_SYMLINK: usize = 3;\n    const ROMFS_BLOCK_DEVICE: usize = 4;\n    const ROMFS_CHAR_DEVICE: usize = 5;\n    const ROMFS_SOCKET: usize = 6;\n    const ROMFS_FIFO: usize = 7;\n\n    let file_header_structure = vec![\n        (\"next_header_offset\", \"u32\"),\n        (\"info\", \"u32\"),\n        (\"size\", \"u32\"),\n        (\"checksum\", \"u32\"),\n    ];\n\n    // Size of the defined file header structure\n    let file_header_size = common::size(&file_header_structure);\n\n    // Parse the file header\n    if let Ok(file_entry_header) = common::parse(romfs_data, &file_header_structure, \"big\") {\n        // Null terminated file name immediately follows the header\n        if let Some(file_name_bytes) = romfs_data.get(file_header_size..) {\n            let file_name = get_cstring(file_name_bytes);\n\n            // A file should have a name\n            if !file_name.is_empty() {\n                // Instantiate a new RomFSEntry structure\n                let mut file_header = RomFSFileHeader {\n                    ..Default::default()\n                };\n\n                // Populate basic info\n                file_header.size = file_entry_header[\"size\"];\n                file_header.info = file_entry_header[\"info\"];\n                file_header.checksum = file_entry_header[\"checksum\"];\n                file_header.name = file_name.clone();\n\n                // File data begins immediately after the file header, including the NULL-terminated, 16-byte alignment padded file name\n                file_header.data_offset = file_header_size + romfs_align(file_name.len() + 1);\n\n                // These values are encoded into the next header offset field\n                file_header.file_type = file_entry_header[\"next_header_offset\"] & FILE_TYPE_MASK;\n                file_header.executable =\n                    (file_entry_header[\"next_header_offset\"] & FILE_EXEC_MASK) != 0;\n\n                // Set the type of entry that this is\n                file_header.fifo = file_header.file_type == ROMFS_FIFO;\n                file_header.socket = file_header.file_type == ROMFS_SOCKET;\n                file_header.symlink = file_header.file_type == ROMFS_SYMLINK;\n                file_header.regular = file_header.file_type == ROMFS_REGULAR_FILE;\n                file_header.directory = file_header.file_type == ROMFS_DIRECTORY;\n                file_header.block_device = file_header.file_type == ROMFS_BLOCK_DEVICE;\n                file_header.character_device = file_header.file_type == ROMFS_CHAR_DEVICE;\n\n                // The next file header offset is an offset from the beginning of the RomFS image\n                file_header.next_header_offset =\n                    file_entry_header[\"next_header_offset\"] & NEXT_OFFSET_MASK;\n\n                return Ok(file_header);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// RomFS aligns things to a 16-byte boundary\nfn romfs_align(x: usize) -> usize {\n    const ALIGNMENT: usize = 16;\n\n    let mut padding: usize = 0;\n    let remainder = x % ALIGNMENT;\n\n    if remainder > 0 {\n        padding = ALIGNMENT - remainder;\n    }\n\n    x + padding\n}\n\n/// Pretty simple checksum used by RomFS\nfn romfs_crc_valid(crc_data: &[u8]) -> bool {\n    let word_size: usize = std::mem::size_of::<u32>();\n\n    // Checksum size must be 4-byte aligned\n    if (crc_data.len() % word_size) == 0 {\n        let mut i: usize = 0;\n        let mut sum: u32 = 0;\n\n        // Sum each word\n        while i < crc_data.len() {\n            sum = sum.wrapping_add(u32::from_be_bytes(\n                crc_data[i..i + word_size].try_into().unwrap(),\n            ));\n            i += word_size;\n        }\n\n        /*\n         * The header checksum is set such that summing the bytes should result in a sum of 0.\n         */\n        return sum == 0;\n    }\n\n    false\n}\n"
  },
  {
    "path": "src/structures/rtk.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store RTK firmware header info\n#[derive(Debug, Default, Clone)]\npub struct RTKHeader {\n    pub image_size: usize,\n    pub header_size: usize,\n}\n\n/// Parses a RTK header\npub fn parse_rtk_header(rtk_data: &[u8]) -> Result<RTKHeader, StructureError> {\n    const MAGIC_SIZE: usize = 4;\n\n    let rtk_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"image_size\", \"u32\"),\n        (\"checksum\", \"u32\"),\n        (\"unknown1\", \"u32\"),\n        (\"header_size\", \"u32\"),\n        (\"unknown2\", \"u32\"),\n        (\"unknown3\", \"u32\"),\n        (\"identifier\", \"u32\"),\n    ];\n\n    let mut result = RTKHeader {\n        ..Default::default()\n    };\n\n    // Parse the header\n    if let Ok(rtk_header) = common::parse(rtk_data, &rtk_structure, \"little\") {\n        result.image_size = rtk_header[\"image_size\"];\n        result.header_size = rtk_header[\"header_size\"] + MAGIC_SIZE;\n        return Ok(result);\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/seama.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store SEAMA firmware header data\npub struct SeamaHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parse a SEAMA firmware header\npub fn parse_seama_header(seama_data: &[u8]) -> Result<SeamaHeader, StructureError> {\n    // SEAMA magic\n    const MAGIC: usize = 0x5EA3A417;\n\n    let seama_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"description_size\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"unknown1\", \"u64\"),\n        (\"unknown2\", \"u64\"),\n    ];\n\n    let mut endianness: &str = \"little\";\n    let available_data = seama_data.len();\n    let header_size: usize = common::size(&seama_structure);\n\n    // Parse the header; try little endian first\n    if let Ok(mut seama_header) = common::parse(seama_data, &seama_structure, endianness) {\n        // If the magic bytes don't match, switch to big endian\n        if seama_header[\"magic\"] != MAGIC {\n            endianness = \"big\";\n            match common::parse(seama_data, &seama_structure, endianness) {\n                Err(_) => {\n                    return Err(StructureError);\n                }\n                Ok(seama_header_be) => {\n                    seama_header = seama_header_be.clone();\n                }\n            }\n        }\n\n        // Sanity check on magic bytes\n        if seama_header[\"magic\"] == MAGIC {\n            let total_header_size = header_size + seama_header[\"description_size\"];\n\n            // Sanity check on total header size\n            if total_header_size >= header_size && available_data >= total_header_size {\n                return Ok(SeamaHeader {\n                    data_size: seama_header[\"data_size\"],\n                    header_size: total_header_size,\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/sevenzip.rs",
    "content": "use crate::common::crc32;\nuse crate::structures::common::{self, StructureError};\n\n/// Struct to store 7zip header info\n#[derive(Debug, Default, Clone)]\npub struct SevenZipHeader {\n    pub header_size: usize,\n    pub major_version: usize,\n    pub minor_version: usize,\n    pub next_header_crc: usize,\n    pub next_header_size: usize,\n    pub next_header_offset: usize,\n}\n\n/// Parse a 7zip header\npub fn parse_7z_header(sevenzip_data: &[u8]) -> Result<SevenZipHeader, StructureError> {\n    // Offset & size constants\n    const SEVENZIP_CRC_START: usize = 12;\n    const SEVENZIP_HEADER_SIZE: usize = 32;\n\n    let sevenzip_structure = vec![\n        (\"magic_p1\", \"u16\"),\n        (\"magic_p2\", \"u32\"),\n        (\"major_version\", \"u8\"),\n        (\"minor_version\", \"u8\"),\n        (\"header_crc\", \"u32\"),\n        (\"next_header_offset\", \"u64\"),\n        (\"next_header_size\", \"u64\"),\n        (\"next_header_crc\", \"u32\"),\n    ];\n\n    // Parse the 7zip header\n    if let Ok(sevenzip_header) = common::parse(sevenzip_data, &sevenzip_structure, \"little\") {\n        // Validate header CRC, which is calculated over the 'next_header_offset', 'next_header_size', and 'next_header_crc' values\n        if let Some(crc_data) = sevenzip_data.get(SEVENZIP_CRC_START..SEVENZIP_HEADER_SIZE) {\n            if crc32(crc_data) == (sevenzip_header[\"header_crc\"] as u32) {\n                return Ok(SevenZipHeader {\n                    header_size: SEVENZIP_HEADER_SIZE,\n                    major_version: sevenzip_header[\"major_version\"],\n                    minor_version: sevenzip_header[\"minor_version\"],\n                    next_header_crc: sevenzip_header[\"next_header_crc\"],\n                    next_header_size: sevenzip_header[\"next_header_size\"],\n                    next_header_offset: sevenzip_header[\"next_header_offset\"],\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/shrs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store SHRS firmware header info\n#[derive(Debug, Default, Clone)]\npub struct SHRSHeader {\n    pub iv: Vec<u8>,\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parses an SHRS header\npub fn parse_shrs_header(shrs_data: &[u8]) -> Result<SHRSHeader, StructureError> {\n    const IV_START: usize = 12;\n    const IV_END: usize = IV_START + 16;\n    const HEADER_SIZE: usize = 0x6DC;\n\n    let shrs_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"unknown1\", \"u32\"),\n        (\"encrypted_data_size\", \"u32\"),\n        // 16-byte IV immediately follows\n    ];\n\n    // Parse the header\n    if let Ok(shrs_header) = common::parse(shrs_data, &shrs_structure, \"big\") {\n        if let Some(iv_bytes) = shrs_data.get(IV_START..IV_END) {\n            return Ok(SHRSHeader {\n                iv: iv_bytes.to_vec(),\n                data_size: shrs_header[\"encrypted_data_size\"],\n                header_size: HEADER_SIZE,\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/squashfs.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Stores SquashFS header info\n#[derive(Debug, Default, Clone)]\npub struct SquashFSHeader {\n    pub timestamp: usize,\n    pub block_size: usize,\n    pub image_size: usize,\n    pub header_size: usize,\n    pub inode_count: usize,\n    pub endianness: String,\n    pub compression: usize,\n    pub major_version: usize,\n    pub minor_version: usize,\n    pub uid_table_start: usize,\n}\n\n/// Parse a SquashFS superblock header\npub fn parse_squashfs_header(sqsh_data: &[u8]) -> Result<SquashFSHeader, StructureError> {\n    // Size & offset constants\n    const MAX_SQUASHFS_VERSION: u16 = 4;\n    const SQUASHFS_VERSION_END: usize = 30;\n    const SQUASHFS_VERSION_START: usize = 28;\n    const MIN_SQUASHFS_HEADER_SIZE: usize = 120;\n\n    let squashfs_v4_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"inode_count\", \"u32\"),\n        (\"modification_time\", \"u32\"),\n        (\"block_size\", \"u32\"),\n        (\"fragment_count\", \"u32\"),\n        (\"compression_id\", \"u16\"),\n        (\"block_log\", \"u16\"),\n        (\"flags\", \"u16\"),\n        (\"id_count\", \"u16\"),\n        (\"major_version\", \"u16\"),\n        (\"minor_version\", \"u16\"),\n        (\"root_inode_ref\", \"u64\"),\n        (\"image_size\", \"u64\"),\n        (\"uid_start\", \"u64\"),\n    ];\n\n    let squashfs_v3_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"inode_count\", \"u32\"),\n        (\"bytes_used_2\", \"u32\"),\n        (\"uid_start_2\", \"u32\"),\n        (\"guid_start_2\", \"u32\"),\n        (\"inode_table_start_2\", \"u32\"),\n        (\"directory_table_start_2\", \"u32\"),\n        (\"major_version\", \"u16\"),\n        (\"minor_version\", \"u16\"),\n        (\"block_size_1\", \"u16\"),\n        (\"block_log\", \"u16\"),\n        (\"flags\", \"u8\"),\n        (\"uid_count\", \"u8\"),\n        (\"guid_count\", \"u8\"),\n        (\"modification_time\", \"u32\"),\n        (\"root_inode_ref\", \"u64\"),\n        (\"block_size\", \"u32\"),\n        (\"fragment_entry_count\", \"u32\"),\n        (\"fragment_table_start_2\", \"u32\"),\n        (\"image_size\", \"u64\"),\n        (\"uid_start\", \"u64\"),\n        (\"guid_start\", \"u64\"),\n        (\"inode_table_start\", \"u64\"),\n        (\"directory_table_start\", \"u64\"),\n        (\"fragment_table_start\", \"u64\"),\n        (\"lookup_table_start\", \"u64\"),\n    ];\n\n    // Default to little endian\n    let mut sqsh_header = SquashFSHeader {\n        endianness: \"little\".to_string(),\n        ..Default::default()\n    };\n\n    // Make sure there is at least enough data to read in a SquashFS header\n    if sqsh_data.len() > MIN_SQUASHFS_HEADER_SIZE {\n        /*\n         * Regardless of the SquashFS version, the version number is always at the same location in the SquashFS suprblock header.\n         * This can then be reliably used to determine both the SquashFS superblock header version, as well as the endianess used.\n         * Interpret the squashfs major version, assuming little endian.\n         */\n        let mut squashfs_version: u16 = u16::from_le_bytes(\n            sqsh_data[SQUASHFS_VERSION_START..SQUASHFS_VERSION_END]\n                .try_into()\n                .unwrap(),\n        );\n\n        // If the version number doesn't look sane, switch to big endian\n        if squashfs_version == 0 || squashfs_version > MAX_SQUASHFS_VERSION {\n            sqsh_header.endianness = \"big\".to_string();\n            squashfs_version = u16::from_be_bytes(\n                sqsh_data[SQUASHFS_VERSION_START..SQUASHFS_VERSION_END]\n                    .try_into()\n                    .unwrap(),\n            );\n        }\n\n        // Sanity check the version number\n        if squashfs_version <= MAX_SQUASHFS_VERSION && squashfs_version > 0 {\n            let squashfs_header_size: usize;\n            let mut squashfs_header: HashMap<String, usize>;\n\n            // Parse the SquashFS header, using the appropriate version header.\n            if squashfs_version == 4 {\n                squashfs_header_size = common::size(&squashfs_v4_structure);\n                match common::parse(sqsh_data, &squashfs_v4_structure, &sqsh_header.endianness) {\n                    Err(e) => {\n                        return Err(e);\n                    }\n                    Ok(squash4_header) => {\n                        squashfs_header = squash4_header.clone();\n                    }\n                }\n            } else {\n                squashfs_header_size = common::size(&squashfs_v3_structure);\n                match common::parse(sqsh_data, &squashfs_v3_structure, &sqsh_header.endianness) {\n                    Err(e) => {\n                        return Err(e);\n                    }\n                    Ok(squash3_header) => {\n                        squashfs_header = squash3_header.clone();\n\n                        // Adjust the reported header values for v1 and v2 images\n                        if squashfs_version < 3 {\n                            squashfs_header\n                                .insert(\"uid_start\".to_string(), squashfs_header[\"uid_start_2\"]);\n                            squashfs_header\n                                .insert(\"guid_start\".to_string(), squashfs_header[\"guid_start_2\"]);\n                            squashfs_header\n                                .insert(\"image_size\".to_string(), squashfs_header[\"bytes_used_2\"]);\n                            squashfs_header.insert(\n                                \"inode_table_start\".to_string(),\n                                squashfs_header[\"inode_table_start_2\"],\n                            );\n                            squashfs_header.insert(\n                                \"directory_table_start\".to_string(),\n                                squashfs_header[\"directory_table_start_2\"],\n                            );\n                        }\n                    }\n                }\n            }\n\n            // Report the total size of this SquashFS image\n            sqsh_header.image_size = squashfs_header[\"image_size\"];\n\n            // Make sure the reported image size is at least bigger than the SquashFS header\n            if sqsh_header.image_size > MIN_SQUASHFS_HEADER_SIZE {\n                // Make sure the block size and block log fields agree\n                if squashfs_header[\"block_size\"] > 0\n                    && squashfs_header[\"block_log\"]\n                        == (squashfs_header[\"block_size\"].ilog2() as usize)\n                {\n                    // Report relevant squashfs fields\n                    sqsh_header.timestamp = squashfs_header[\"modification_time\"];\n                    sqsh_header.block_size = squashfs_header[\"block_size\"];\n                    sqsh_header.header_size = squashfs_header_size;\n                    sqsh_header.inode_count = squashfs_header[\"inode_count\"];\n                    sqsh_header.major_version = squashfs_header[\"major_version\"];\n                    sqsh_header.minor_version = squashfs_header[\"minor_version\"];\n                    sqsh_header.uid_table_start = squashfs_header[\"uid_start\"];\n\n                    // v3 headers don't have a compression ID\n                    if squashfs_header.contains_key(\"compression_id\") {\n                        sqsh_header.compression = squashfs_header[\"compression_id\"];\n                    }\n\n                    return Ok(sqsh_header);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Parse a UID entry for either SquashFSv4 or SquashFSv3\npub fn parse_squashfs_uid_entry(\n    uid_data: &[u8],\n    version: usize,\n    endianness: &str,\n) -> Result<usize, StructureError> {\n    let squashfs_v4_uid_table_structure = vec![(\"uid_block_ptr\", \"u64\")];\n    let squashfs_v3_uid_table_structure = vec![(\"uid_block_ptr\", \"u32\")];\n\n    // Parse one entry from the UID table\n    if version == 4 {\n        match common::parse(uid_data, &squashfs_v4_uid_table_structure, endianness) {\n            Err(e) => Err(e),\n            Ok(uidv4) => Ok(uidv4[\"uid_block_ptr\"]),\n        }\n    } else {\n        match common::parse(uid_data, &squashfs_v3_uid_table_structure, endianness) {\n            Err(e) => Err(e),\n            Ok(uidv3) => Ok(uidv3[\"uid_block_ptr\"]),\n        }\n    }\n}\n"
  },
  {
    "path": "src/structures/svg.rs",
    "content": "use crate::structures::common::StructureError;\nuse aho_corasick::AhoCorasick;\n\nconst SVG_OPEN_TAG: &[u8] = b\"<svg \";\nconst SVG_CLOSE_TAG: &[u8] = b\"</svg>\";\nconst SVG_HEAD_MAGIC: &str = \"xmlns=\\\"http://www.w3.org/2000/svg\\\"\";\n\n/// Stores info about an SVG image\n#[derive(Debug, Default, Clone)]\npub struct SVGImage {\n    pub total_size: usize,\n}\n\n/// Parse an SVG image to determine its total size\npub fn parse_svg_image(svg_data: &[u8]) -> Result<SVGImage, StructureError> {\n    let mut head_tag_count: usize = 0;\n    let mut unclosed_svg_tags: usize = 0;\n\n    let svg_tags = vec![SVG_OPEN_TAG, SVG_CLOSE_TAG];\n\n    let grep = AhoCorasick::new(svg_tags).unwrap();\n\n    // Need to search through the data to find all <svg ...> and </svg> tags.\n    // There may be multiple of these tags in any given SVG image.\n    for tag_match in grep.find_overlapping_iter(svg_data) {\n        let tag_start: usize = tag_match.start();\n\n        match parse_svg_tag(&svg_data[tag_start..]) {\n            Err(_) => {\n                break;\n            }\n            Ok(svg_tag) => {\n                if svg_tag.is_head {\n                    head_tag_count += 1;\n                }\n\n                if svg_tag.is_open {\n                    unclosed_svg_tags += 1;\n                }\n\n                if svg_tag.is_close {\n                    unclosed_svg_tags -= 1;\n                }\n\n                // There should be only one head tag\n                if head_tag_count > 1 {\n                    break;\n                }\n\n                // If one head tag was found and all svg tags are closed, that's EOF\n                if head_tag_count == 1 && unclosed_svg_tags == 0 {\n                    return Ok(SVGImage {\n                        total_size: tag_start + SVG_CLOSE_TAG.len(),\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a parsed SVG tag\n#[derive(Debug, Default, Clone)]\nstruct SVGTag {\n    pub is_head: bool,\n    pub is_open: bool,\n    pub is_close: bool,\n}\n\n/// Parse an individual SVG tag\nfn parse_svg_tag(tag_data: &[u8]) -> Result<SVGTag, StructureError> {\n    const END_TAG: u8 = 0x3E;\n\n    let mut result = SVGTag {\n        ..Default::default()\n    };\n\n    let svg_open_tag = String::from_utf8(SVG_OPEN_TAG.to_vec()).unwrap();\n    let svg_close_tag = String::from_utf8(SVG_CLOSE_TAG.to_vec()).unwrap();\n    let svg_head_string = SVG_HEAD_MAGIC.to_string();\n\n    // Tags are expected to start with '<svg' or </svg>', and end with '>'\n    for i in 0..tag_data.len() {\n        if tag_data[i] == END_TAG {\n            if let Some(tag_bytes) = tag_data.get(0..i + 1) {\n                if let Ok(tag_string) = String::from_utf8(tag_bytes.to_vec()) {\n                    result.is_open = tag_string.starts_with(&svg_open_tag);\n                    result.is_close = tag_string.starts_with(&svg_close_tag);\n                    result.is_head = tag_string.contains(&svg_head_string);\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/tplink.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info about a TP-Link firmware header\n#[derive(Debug, Default, Clone)]\npub struct TPLinkFirmwareHeader {\n    pub header_size: usize,\n    pub kernel_load_address: usize,\n    pub kernel_entry_point: usize,\n}\n\n/// Pase a TP-Link firmware header\npub fn parse_tplink_header(tplink_data: &[u8]) -> Result<TPLinkFirmwareHeader, StructureError> {\n    // Offset of data structure, after firmware signature\n    const STRUCTURE_OFFSET: usize = 0x40;\n\n    // Total size of the firmware header\n    const HEADER_SIZE: usize = 0x200;\n\n    // https://github.com/jtreml/firmware-mod-kit/blob/master/src/tpl-tool/doc/Image_layout\n    let tplink_structure = vec![\n        (\"product_id\", \"u32\"),\n        (\"product_version\", \"u32\"),\n        (\"reserved1\", \"u32\"),\n        (\"image_checksum_p1\", \"u64\"),\n        (\"image_checksum_p2\", \"u64\"),\n        (\"reserved2\", \"u32\"),\n        (\"kernel_checksum_p1\", \"u64\"),\n        (\"kernel_checksum_p2\", \"u64\"),\n        (\"reserved3\", \"u32\"),\n        (\"kernel_load_address\", \"u32\"),\n        (\"kernel_entry_point\", \"u32\"),\n        (\"image_length\", \"u32\"),\n        (\"kernel_offset\", \"u32\"),\n        (\"kernel_length\", \"u32\"),\n        (\"rootfs_offset\", \"u32\"),\n        (\"rootfs_length\", \"u32\"),\n        (\"bootloader_offset\", \"u32\"),\n        (\"bootloader_length\", \"u32\"),\n        (\"fw_version_major\", \"u16\"),\n        (\"fw_version_minor\", \"u16\"),\n        (\"fw_version_patch\", \"u16\"),\n        (\"reserved4\", \"u32\"),\n    ];\n\n    let mut result = TPLinkFirmwareHeader {\n        header_size: HEADER_SIZE,\n        ..Default::default()\n    };\n\n    // Sanity check available data\n    if tplink_data.len() >= HEADER_SIZE {\n        if let Some(structure_data) = tplink_data.get(STRUCTURE_OFFSET..) {\n            // Parse the header\n            if let Ok(tplink_header) = common::parse(structure_data, &tplink_structure, \"little\") {\n                // Make sure the reserved fields are NULL\n                if tplink_header[\"reserved1\"] == 0\n                    && tplink_header[\"reserved2\"] == 0\n                    && tplink_header[\"reserved3\"] == 0\n                    && tplink_header[\"reserved4\"] == 0\n                {\n                    // Unfortunately, most header fields aren't reliably used; these seem to be, so report them\n                    result.kernel_entry_point = tplink_header[\"kernel_entry_point\"];\n                    result.kernel_load_address = tplink_header[\"kernel_load_address\"];\n                    return Ok(result);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a TP-Link RTOS firmware header\n#[derive(Debug, Default, Clone)]\npub struct TPLinkRTOSFirmwareHeader {\n    pub header_size: usize,\n    pub total_size: usize,\n    pub model_number: usize,\n    pub hardware_rev_major: usize,\n    pub hardware_rev_minor: usize,\n}\n\n/// Parse a TP-Link RTOS firmware header\npub fn parse_tplink_rtos_header(\n    tplink_data: &[u8],\n) -> Result<TPLinkRTOSFirmwareHeader, StructureError> {\n    const HEADER_SIZE: usize = 0x94;\n    const MAGIC2_VALUE: usize = 0x494D4730;\n    const TOTAL_SIZE_OFFSET: usize = 20;\n\n    let tplink_rtos_structure = vec![\n        (\"magic1\", \"u32\"),\n        (\"unknown1\", \"u64\"),\n        (\"unknown2\", \"u64\"),\n        (\"magic2\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"model_number\", \"u16\"),\n        (\"hardware_revision_major\", \"u8\"),\n        (\"hardware_revision_minor\", \"u8\"),\n    ];\n\n    if let Ok(header) = common::parse(tplink_data, &tplink_rtos_structure, \"big\") {\n        if header[\"magic2\"] == MAGIC2_VALUE {\n            return Ok(TPLinkRTOSFirmwareHeader {\n                header_size: HEADER_SIZE,\n                total_size: header[\"data_size\"] + TOTAL_SIZE_OFFSET,\n                model_number: header[\"model_number\"],\n                hardware_rev_major: header[\"hardware_revision_major\"],\n                hardware_rev_minor: header[\"hardware_revision_minor\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/trx.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores TRX firmware header info\n#[derive(Debug, Clone, Default)]\npub struct TRXHeader {\n    pub version: usize,\n    pub checksum: usize,\n    pub total_size: usize,\n    pub header_size: usize,\n    pub partitions: Vec<usize>,\n}\n\n/// Parse a TRX firmware header\npub fn parse_trx_header(header_data: &[u8]) -> Result<TRXHeader, StructureError> {\n    // TRX comes in two flavors: v1 and v2\n    const TRX_VERSION_2: usize = 2;\n\n    let trx_header_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"total_size\", \"u32\"),\n        (\"crc32\", \"u32\"),\n        (\"flags\", \"u16\"),\n        (\"version\", \"u16\"),\n        (\"partition1_offset\", \"u32\"),\n        (\"partition2_offset\", \"u32\"),\n        (\"partition3_offset\", \"u32\"),\n        (\"partition4_offset\", \"u32\"),\n    ];\n\n    let allowed_versions: Vec<usize> = vec![1, 2];\n\n    // Size of the fixed-length portion of the header structure\n    let mut struct_size: usize = common::size(&trx_header_structure);\n\n    // Parse the header\n    if let Ok(trx_header) = common::parse(header_data, &trx_header_structure, \"little\") {\n        // Sanity check partition offsets. Partition offsets may be 0.\n        if trx_header[\"partition1_offset\"] <= trx_header[\"total_size\"]\n            && trx_header[\"partition2_offset\"] <= trx_header[\"total_size\"]\n            && trx_header[\"partition3_offset\"] <= trx_header[\"total_size\"]\n        {\n            // Sanity check the reported total size\n            if trx_header[\"total_size\"] > struct_size {\n                // Sanity check the reported version number\n                if allowed_versions.contains(&trx_header[\"version\"]) {\n                    let mut partitions: Vec<usize> = vec![];\n\n                    if trx_header[\"partition1_offset\"] != 0 {\n                        partitions.push(trx_header[\"partition1_offset\"]);\n                    }\n\n                    if trx_header[\"partition2_offset\"] != 0 {\n                        partitions.push(trx_header[\"partition2_offset\"]);\n                    }\n\n                    if trx_header[\"partition3_offset\"] != 0 {\n                        partitions.push(trx_header[\"partition3_offset\"]);\n                    }\n\n                    // Only TRXv2 has a fourth partition entry\n                    if trx_header[\"version\"] == TRX_VERSION_2 {\n                        if trx_header[\"partition4_offset\"] != 0 {\n                            partitions.push(trx_header[\"partition4_offset\"]);\n                        }\n                    } else {\n                        // For TRXv1, this means the real structure size is 4 bytes shorter\n                        struct_size -= std::mem::size_of::<u32>();\n                    }\n\n                    return Ok(TRXHeader {\n                        version: trx_header[\"version\"],\n                        checksum: trx_header[\"crc32\"],\n                        total_size: trx_header[\"total_size\"],\n                        header_size: struct_size,\n                        partitions: partitions.clone(),\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/ubi.rs",
    "content": "use crate::common::crc32;\nuse crate::structures::common::{self, StructureError};\n\n/// Stores UBI superblock header info\n#[derive(Debug, Default, Clone)]\npub struct UbiSuperBlockHeader {\n    pub leb_size: usize,\n    pub leb_count: usize,\n}\n\n/// Partially parse a UBI superblock header\npub fn parse_ubi_superblock_header(ubi_data: &[u8]) -> Result<UbiSuperBlockHeader, StructureError> {\n    // Type & offset constants\n    const MAX_GROUP_TYPE: usize = 2;\n    const CRC_START_OFFSET: usize = 8;\n    const SUPERBLOCK_NODE_TYPE: usize = 6;\n\n    // There are some other fields in the superblock header that we don't parse because we don't really care about them...\n    const SUPERBLOCK_STRUCTURE_EXTRA_SIZE: usize = 3968;\n\n    let ubi_sb_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"header_crc\", \"u32\"),\n        (\"sequence_number\", \"u64\"),\n        (\"node_len\", \"u32\"),\n        (\"node_type\", \"u8\"),\n        (\"group_type\", \"u8\"),\n        (\"padding1\", \"u32\"),\n        (\"key_hash\", \"u8\"),\n        (\"key_format\", \"u8\"),\n        (\"flags\", \"u32\"),\n        (\"min_io_size\", \"u32\"),\n        (\"leb_size\", \"u32\"),\n        (\"leb_count\", \"u32\"),\n        (\"max_leb_count\", \"u32\"),\n        (\"max_bud_bytes\", \"u64\"),\n        (\"log_lebs\", \"u32\"),\n        (\"lpt_lebs\", \"u32\"),\n        (\"orph_lebs\", \"u32\"),\n        (\"jhead_count\", \"u32\"),\n        (\"fanout\", \"u32\"),\n        (\"lsave_count\", \"u32\"),\n        (\"fmt_version\", \"u32\"),\n        (\"default_compression\", \"u16\"),\n        (\"padding2\", \"u16\"),\n        (\"rp_uid\", \"u32\"),\n        (\"rp_gid\", \"u32\"),\n        (\"rp_size\", \"u64\"),\n        (\"time_gran\", \"u32\"),\n        (\"uuid_p1\", \"u64\"),\n        (\"uuid_p2\", \"u64\"),\n        (\"ro_compat_version\", \"u32\"),\n    ];\n\n    let sb_struct_size: usize = common::size(&ubi_sb_structure) + SUPERBLOCK_STRUCTURE_EXTRA_SIZE;\n\n    // Parse the UBI superblock header\n    if let Ok(sb_header) = common::parse(ubi_data, &ubi_sb_structure, \"little\") {\n        // Make sure the padding fields are NULL\n        if sb_header[\"padding1\"] == 0 && sb_header[\"padding2\"] == 0 {\n            // Make sure the node type is SUPERBLOCK\n            if sb_header[\"node_type\"] == SUPERBLOCK_NODE_TYPE {\n                // Make sure the group type is valid\n                if sb_header[\"group_type\"] <= MAX_GROUP_TYPE {\n                    // Validate the header CRC, which is calculated over the entire header except for the magic bytes and CRC field\n                    if let Some(crc_data) = ubi_data.get(CRC_START_OFFSET..sb_struct_size) {\n                        if ubi_crc(crc_data) == sb_header[\"header_crc\"] {\n                            return Ok(UbiSuperBlockHeader {\n                                leb_size: sb_header[\"leb_size\"],\n                                leb_count: sb_header[\"leb_count\"],\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a UBI erase count header\n#[derive(Debug, Default, Clone)]\npub struct UbiECHeader {\n    pub version: usize,\n    pub data_offset: usize,\n    pub volume_id_offset: usize,\n}\n\n/// Parse a UBI erase count header\npub fn parse_ubi_ec_header(ubi_data: &[u8]) -> Result<UbiECHeader, StructureError> {\n    let ubi_ec_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"version\", \"u8\"),\n        (\"padding1\", \"u24\"),\n        (\"ec\", \"u64\"),\n        (\"volume_id_header_offset\", \"u32\"),\n        (\"data_offset\", \"u32\"),\n        (\"image_sequence_number\", \"u32\"),\n        (\"padding2\", \"u64\"),\n        (\"padding3\", \"u64\"),\n        (\"padding4\", \"u64\"),\n        (\"padding5\", \"u64\"),\n        (\"header_crc\", \"u32\"),\n    ];\n\n    let ec_header_size: usize = common::size(&ubi_ec_structure);\n    let crc_data_size: usize = ec_header_size - std::mem::size_of::<u32>();\n\n    // Parse the first half of the header\n    if let Ok(ubi_ec_header) = common::parse(ubi_data, &ubi_ec_structure, \"big\") {\n        // Offsets should be beyond the EC header\n        if ubi_ec_header[\"data_offset\"] >= ec_header_size\n            && ubi_ec_header[\"volume_id_header_offset\"] >= ec_header_size\n        {\n            // Validate the header CRC\n            if let Some(crc_data) = ubi_data.get(0..crc_data_size) {\n                if ubi_crc(crc_data) == ubi_ec_header[\"header_crc\"] {\n                    return Ok(UbiECHeader {\n                        version: ubi_ec_header[\"version\"],\n                        data_offset: ubi_ec_header[\"data_offset\"],\n                        volume_id_offset: ubi_ec_header[\"volume_id_header_offset\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Dummy structure indicating a UBI volume header was parsed successfully\n#[derive(Debug, Default, Clone)]\npub struct UbiVolumeHeader;\n\n/// Parse a UBI volume header\npub fn parse_ubi_volume_header(ubi_data: &[u8]) -> Result<UbiVolumeHeader, StructureError> {\n    let ubi_vol_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"version\", \"u8\"),\n        (\"volume_type\", \"u8\"),\n        (\"copy_flag\", \"u8\"),\n        (\"compat_type\", \"u8\"),\n        (\"volume_id\", \"u32\"),\n        (\"logical_erase_block_number\", \"u32\"),\n        (\"padding1\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"used_erase_block_count\", \"u32\"),\n        (\"data_padding_size\", \"u32\"),\n        (\"data_crc\", \"u32\"),\n        (\"padding2\", \"u32\"),\n        (\"sequence_number\", \"u64\"),\n        (\"padding3\", \"u64\"),\n        (\"padding4\", \"u32\"),\n        (\"header_crc\", \"u32\"),\n    ];\n\n    let vol_header_size: usize = common::size(&ubi_vol_structure);\n    let crc_data_size: usize = vol_header_size - std::mem::size_of::<u32>();\n\n    // Parse the volume header\n    if let Ok(ubi_vol_header) = common::parse(ubi_data, &ubi_vol_structure, \"big\") {\n        // Sanity check padding fields, they should all be null\n        if ubi_vol_header[\"padding1\"] == 0\n            && ubi_vol_header[\"padding2\"] == 0\n            && ubi_vol_header[\"padding3\"] == 0\n            && ubi_vol_header[\"padding4\"] == 0\n        {\n            // Validate the header CRC\n            if let Some(crc_data) = ubi_data.get(0..crc_data_size) {\n                if ubi_crc(crc_data) == ubi_vol_header[\"header_crc\"] {\n                    return Ok(UbiVolumeHeader);\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Calculate a UBI checksum\nfn ubi_crc(data: &[u8]) -> usize {\n    const UBI_CRC_INIT: u32 = 0xFFFFFFFF;\n    ((!crc32(data)) & UBI_CRC_INIT) as usize\n}\n"
  },
  {
    "path": "src/structures/uefi.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info about a UEFI volume header\n#[derive(Debug, Default, Clone)]\npub struct UEFIVolumeHeader {\n    pub header_crc: usize,\n    pub header_size: usize,\n    pub volume_size: usize,\n}\n\n/// Parse a UEFI volume header\npub fn parse_uefi_volume_header(uefi_data: &[u8]) -> Result<UEFIVolumeHeader, StructureError> {\n    // The revision field must be 1 or 2\n    let valid_revisions: Vec<usize> = vec![1, 2];\n\n    let uefi_pi_header_structure = vec![\n        (\"volume_size\", \"u64\"),\n        (\"magic\", \"u32\"),\n        (\"attributes\", \"u32\"),\n        (\"header_size\", \"u16\"),\n        (\"header_crc\", \"u16\"),\n        (\"extended_header_offset\", \"u16\"),\n        (\"reserved\", \"u8\"),\n        (\"revision\", \"u8\"),\n    ];\n\n    // Parse the volume header\n    if let Ok(uefi_volume_header) = common::parse(uefi_data, &uefi_pi_header_structure, \"little\") {\n        // Make sure the header size is sane (must be smaller than the total volume size)\n        if uefi_volume_header[\"header_size\"] < uefi_volume_header[\"volume_size\"] {\n            // The reserved field *must* be 0\n            if uefi_volume_header[\"reserved\"] == 0 {\n                // The revision number must be 1 or 2\n                if valid_revisions.contains(&uefi_volume_header[\"revision\"]) {\n                    return Ok(UEFIVolumeHeader {\n                        // TODO: Validate UEFI header CRC\n                        header_crc: uefi_volume_header[\"header_crc\"],\n                        header_size: uefi_volume_header[\"header_size\"],\n                        volume_size: uefi_volume_header[\"volume_size\"],\n                    });\n                }\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a UEFI capsule header\n#[derive(Debug, Default, Clone)]\npub struct UEFICapsuleHeader {\n    pub total_size: usize,\n    pub header_size: usize,\n}\n\n/// Parse  UEFI capsule header\npub fn parse_uefi_capsule_header(uefi_data: &[u8]) -> Result<UEFICapsuleHeader, StructureError> {\n    let uefi_capsule_structure = vec![\n        (\"guid_p1\", \"u64\"),\n        (\"guid_p2\", \"u64\"),\n        (\"header_size\", \"u32\"),\n        (\"flags\", \"u32\"),\n        (\"total_size\", \"u32\"),\n    ];\n\n    // Parse the capsule header\n    if let Ok(capsule_header) = common::parse(uefi_data, &uefi_capsule_structure, \"little\") {\n        // Sanity check on header and total size fields\n        if capsule_header[\"header_size\"] < capsule_header[\"total_size\"] {\n            return Ok(UEFICapsuleHeader {\n                total_size: capsule_header[\"total_size\"],\n                header_size: capsule_header[\"header_size\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/uimage.rs",
    "content": "use crate::common::{crc32, get_cstring};\nuse crate::structures::common::{self, StructureError};\nuse std::collections::HashMap;\n\n/// Stores info about a uImage header\n#[derive(Debug, Default, Clone)]\npub struct UImageHeader {\n    pub header_size: usize,\n    pub name: String,\n    pub data_size: usize,\n    pub data_checksum: usize,\n    pub load_address: usize,\n    pub entry_point_address: usize,\n    pub timestamp: usize,\n    pub compression_type: String,\n    pub cpu_type: String,\n    pub os_type: String,\n    pub image_type: String,\n    pub header_crc_valid: bool,\n}\n\n/// Pase a uImage header\npub fn parse_uimage_header(uimage_data: &[u8]) -> Result<UImageHeader, StructureError> {\n    const UIMAGE_HEADER_SIZE: usize = 64;\n    const UIMAGE_NAME_OFFSET: usize = 32;\n\n    let uimage_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"header_crc\", \"u32\"),\n        (\"creation_timestamp\", \"u32\"),\n        (\"data_size\", \"u32\"),\n        (\"load_address\", \"u32\"),\n        (\"entry_point_address\", \"u32\"),\n        (\"data_crc\", \"u32\"),\n        (\"os_type\", \"u8\"),\n        (\"cpu_type\", \"u8\"),\n        (\"image_type\", \"u8\"),\n        (\"compression_type\", \"u8\"),\n    ];\n\n    let valid_os_types = HashMap::from([\n        (1, \"OpenBSD\"),\n        (2, \"NetBSD\"),\n        (3, \"FreeBSD\"),\n        (4, \"4.4BSD\"),\n        (5, \"Linux\"),\n        (6, \"SVR4\"),\n        (7, \"Esix\"),\n        (8, \"Solaris\"),\n        (9, \"Irix\"),\n        (10, \"SCO\"),\n        (11, \"Dell\"),\n        (12, \"NCR\"),\n        (13, \"LynxOS\"),\n        (14, \"VxWorks\"),\n        (15, \"pSOS\"),\n        (16, \"QNX\"),\n        (17, \"Firmware\"),\n        (18, \"RTEMS\"),\n        (19, \"ARTOS\"),\n        (20, \"Unity OS\"),\n        (21, \"INTEGRITY\"),\n        (22, \"OSE\"),\n        (23, \"Plan 9\"),\n        (24, \"OpenRTOS\"),\n        (25, \"ARM Trusted Firmware\"),\n        (26, \"Trusted Execution Environment\"),\n        (27, \"OpenSBI\"),\n        (28, \"EFI Firmware\"),\n        (29, \"ELF Image\"),\n    ]);\n\n    let valid_cpu_types = HashMap::from([\n        (1, \"Alpha\"),\n        (2, \"ARM\"),\n        (3, \"Intel x86\"),\n        (4, \"IA64\"),\n        (5, \"MIPS32\"),\n        (6, \"MIPS64\"),\n        (7, \"PowerPC\"),\n        (8, \"IBM S390\"),\n        (10, \"SuperH\"),\n        (11, \"Sparc\"),\n        (12, \"Sparc64\"),\n        (13, \"M68K\"),\n        (14, \"Nios-32\"),\n        (15, \"MicroBlaze\"),\n        (16, \"Nios-II\"),\n        (17, \"Blackfin\"),\n        (18, \"AVR32\"),\n        (19, \"ST200\"),\n        (20, \"Sandbox\"),\n        (21, \"NDS32\"),\n        (22, \"OpenRISC\"),\n        (23, \"ARM64\"),\n        (24, \"ARC\"),\n        (25, \"x86-64\"),\n        (26, \"Xtensa\"),\n        (27, \"RISC-V\"),\n    ]);\n\n    let valid_compression_types = HashMap::from([\n        (0, \"none\"),\n        (1, \"gzip\"),\n        (2, \"bzip2\"),\n        (3, \"lzma\"),\n        (4, \"lzo\"),\n        (5, \"lz4\"),\n        (6, \"zstd\"),\n    ]);\n\n    let valid_image_types = HashMap::from([\n        (1, \"Standalone Program\"),\n        (2, \"OS Kernel Image\"),\n        (3, \"RAMDisk Image\"),\n        (4, \"Multi-File Image\"),\n        (5, \"Firmware Image\"),\n        (6, \"Script file\"),\n        (7, \"Filesystem Image\"),\n        (8, \"Binary Flat Device Tree Blob\"),\n        (9, \"Kirkwood Boot Image\"),\n        (10, \"Freescale IMXBoot Image\"),\n        (11, \"Davinci UBL Image\"),\n        (12, \"TI OMAP Config Header Image\"),\n        (13, \"TI Davinci AIS Image\"),\n        (14, \"OS Kernel Image\"),\n        (15, \"Freescale PBL Boot Image\"),\n        (16, \"Freescale MXSBoot Image\"),\n        (17, \"TI Keystone GPHeader Image\"),\n        (18, \"ATMEL ROM bootable Image\"),\n        (19, \"Altera SOCFPGA CV/AV Preloader\"),\n        (20, \"x86 setup.bin Image\"),\n        (21, \"x86 setup.bin Image\"),\n        (22, \"A list of typeless images\"),\n        (23, \"Rockchip Boot Image\"),\n        (24, \"Rockchip SD card\"),\n        (25, \"Rockchip SPI image\"),\n        (26, \"Xilinx Zynq Boot Image\"),\n        (27, \"Xilinx ZynqMP Boot Image\"),\n        (28, \"Xilinx ZynqMP Boot Image (bif)\"),\n        (29, \"FPGA Image\"),\n        (30, \"VYBRID .vyb Image\"),\n        (31, \"Trusted Execution Environment OS Image\"),\n        (32, \"Firmware Image with HABv4 IVT\"),\n        (33, \"TI Power Management Micro-Controller Firmware\"),\n        (34, \"STMicroelectronics STM32 Image\"),\n        (35, \"Altera SOCFPGA A10 Preloader\"),\n        (36, \"MediaTek BootROM loadable Image\"),\n        (37, \"Freescale IMX8MBoot Image\"),\n        (38, \"Freescale IMX8Boot Image\"),\n        (39, \"Coprocessor Image for remoteproc\"),\n        (40, \"Allwinner eGON Boot Image\"),\n        (41, \"Allwinner TOC0 Boot Image\"),\n        (42, \"Binary Flat Device Tree Blob in a Legacy Image\"),\n        (43, \"Renesas SPKG image\"),\n        (44, \"StarFive SPL image\"),\n    ]);\n\n    // Parse the first half of the header\n    if let Ok(uimage_header) = common::parse(uimage_data, &uimage_structure, \"big\") {\n        // Sanity check header fields\n        if valid_os_types.contains_key(&uimage_header[\"os_type\"])\n            && valid_cpu_types.contains_key(&uimage_header[\"cpu_type\"])\n            && valid_image_types.contains_key(&uimage_header[\"image_type\"])\n            && valid_compression_types.contains_key(&uimage_header[\"compression_type\"])\n        {\n            // Get the header bytes to validate the CRC\n            if let Some(crc_data) = uimage_data.get(0..UIMAGE_HEADER_SIZE) {\n                return Ok(UImageHeader {\n                    header_size: UIMAGE_HEADER_SIZE,\n                    name: get_cstring(&uimage_data[UIMAGE_NAME_OFFSET..]),\n                    data_size: uimage_header[\"data_size\"],\n                    data_checksum: uimage_header[\"data_crc\"],\n                    timestamp: uimage_header[\"creation_timestamp\"],\n                    load_address: uimage_header[\"load_address\"],\n                    entry_point_address: uimage_header[\"entry_point_address\"],\n                    compression_type: valid_compression_types[&uimage_header[\"compression_type\"]]\n                        .to_string(),\n                    cpu_type: valid_cpu_types[&uimage_header[\"cpu_type\"]].to_string(),\n                    os_type: valid_os_types[&uimage_header[\"os_type\"]].to_string(),\n                    image_type: valid_image_types[&uimage_header[\"image_type\"]].to_string(),\n                    header_crc_valid: calculate_uimage_header_checksum(crc_data)\n                        == uimage_header[\"header_crc\"],\n                });\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// uImage checksum calculator\nfn calculate_uimage_header_checksum(hdr: &[u8]) -> usize {\n    const HEADER_CRC_START: usize = 4;\n    const HEADER_CRC_END: usize = 8;\n\n    // Header checksum has to be nulled out to calculate the CRC\n    let mut uimage_header: Vec<u8> = hdr.to_vec();\n\n    for crc_byte in uimage_header\n        .iter_mut()\n        .take(HEADER_CRC_END)\n        .skip(HEADER_CRC_START)\n    {\n        *crc_byte = 0;\n    }\n\n    crc32(&uimage_header) as usize\n}\n"
  },
  {
    "path": "src/structures/vxworks.rs",
    "content": "use crate::structures::common::{self, StructureError};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\n/// Stores info about a single VxWorks symbol table entry\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct VxWorksSymbolTableEntry {\n    pub size: usize,\n    pub name: usize,\n    pub value: usize,\n    pub symtype: String,\n}\n\n/// Parse a single VxWorks symbol table entry\npub fn parse_symtab_entry(\n    symbol_data: &[u8],\n    endianness: &str,\n) -> Result<VxWorksSymbolTableEntry, StructureError> {\n    // This *seems* to be the correct structure for a symbol table entry, it may be different for different VxWorks versions...\n    let symtab_structure = vec![\n        (\"name_ptr\", \"u32\"),\n        (\"value_ptr\", \"u32\"),\n        (\"type\", \"u32\"),\n        (\"group\", \"u32\"),\n    ];\n\n    // There may be more types; these are the only ones I've found in the wild\n    let allowed_symbol_types: HashMap<usize, String> = HashMap::from([\n        (0x500, \"function\".to_string()),\n        (0x700, \"initialized data\".to_string()),\n        (0x900, \"uninitialized data\".to_string()),\n    ]);\n\n    let symtab_structure_size: usize = common::size(&symtab_structure);\n\n    // Parse the symbol table entry\n    if let Ok(symbol_entry) = common::parse(symbol_data, &symtab_structure, endianness) {\n        // Sanity check expected values in the symbol table entry\n        if allowed_symbol_types.contains_key(&symbol_entry[\"type\"])\n            && symbol_entry[\"name_ptr\"] != 0\n            && symbol_entry[\"value_ptr\"] != 0\n        {\n            return Ok(VxWorksSymbolTableEntry {\n                size: symtab_structure_size,\n                name: symbol_entry[\"name_ptr\"],\n                value: symbol_entry[\"value_ptr\"],\n                symtype: allowed_symbol_types[&symbol_entry[\"type\"]].clone(),\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Detect a symbol table entry's endianness\npub fn get_symtab_endianness(symbol_data: &[u8]) -> Result<String, StructureError> {\n    const TYPE_FIELD_OFFSET: usize = 9;\n\n    let mut endianness = \"little\";\n\n    // The type field starts at offset 8 and is 0x00_00_05_00, so for big endian targets the 9th byte will be NULL\n    if let Some(offset_field) = symbol_data.get(TYPE_FIELD_OFFSET) {\n        if *offset_field == 0 {\n            endianness = \"big\";\n        }\n\n        return Ok(endianness.to_string());\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/wince.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Struct to store WindowsCE header info\n#[derive(Debug, Default, Clone)]\npub struct WinCEHeader {\n    pub base_address: usize,\n    pub image_size: usize,\n    pub header_size: usize,\n}\n\n/// Parses a Windows CE header\npub fn parse_wince_header(wince_data: &[u8]) -> Result<WinCEHeader, StructureError> {\n    let wince_header_structure = vec![\n        (\"magic_p1\", \"u32\"),\n        (\"magic_p2\", \"u24\"),\n        (\"image_start\", \"u32\"),\n        (\"image_size\", \"u32\"),\n    ];\n\n    // Parse the WinCE header\n    if let Ok(wince_header) = common::parse(wince_data, &wince_header_structure, \"little\") {\n        return Ok(WinCEHeader {\n            base_address: wince_header[\"image_start\"],\n            image_size: wince_header[\"image_size\"],\n            header_size: common::size(&wince_header_structure),\n        });\n    }\n\n    Err(StructureError)\n}\n\n/// Struct to store WindowsCE block info\n#[derive(Debug, Default, Clone)]\npub struct WinCEBlock {\n    pub address: usize,\n    pub data_size: usize,\n    pub header_size: usize,\n}\n\n/// Parse a WindowsCE block header\npub fn parse_wince_block_header(block_data: &[u8]) -> Result<WinCEBlock, StructureError> {\n    let wince_block_structure = vec![(\"address\", \"u32\"), (\"size\", \"u32\"), (\"checksum\", \"u32\")];\n\n    if let Ok(block_header) = common::parse(block_data, &wince_block_structure, \"little\") {\n        return Ok(WinCEBlock {\n            address: block_header[\"address\"],\n            data_size: block_header[\"size\"],\n            header_size: common::size(&wince_block_structure),\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/xz.rs",
    "content": "use crate::common::crc32;\nuse crate::structures::common::{self, StructureError};\n\n/// Parse and validate an XZ header, returns the header size\npub fn parse_xz_header(xz_data: &[u8]) -> Result<usize, StructureError> {\n    const XZ_CRC_END: usize = 8;\n    const XZ_CRC_START: usize = 6;\n    const XZ_HEADER_SIZE: usize = 12;\n\n    let xz_structure = vec![\n        (\"magic_p1\", \"u32\"),\n        (\"magic_p2\", \"u16\"),\n        (\"flags\", \"u16\"),\n        (\"header_crc\", \"u32\"),\n    ];\n\n    if let Ok(xz_header) = common::parse(xz_data, &xz_structure, \"little\") {\n        if let Some(crc_data) = xz_data.get(XZ_CRC_START..XZ_CRC_END) {\n            if crc32(crc_data) == (xz_header[\"header_crc\"] as u32) {\n                return Ok(XZ_HEADER_SIZE);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/yaffs.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info about a YAFFS object\n#[derive(Debug, Default, Clone)]\npub struct YAFFSObject {\n    // All that is needed for now is the object type; this may be updated in the future as necessary\n    pub obj_type: usize,\n}\n\n/// Partially parse a YAFFS object header\npub fn parse_yaffs_obj_header(\n    header_data: &[u8],\n    endianness: &str,\n) -> Result<YAFFSObject, StructureError> {\n    // The name checksum field is unused and should be 0xFFFF\n    const UNUSED: usize = 0xFFFF;\n\n    // First part of an object header\n    let yaffs_object_structure = vec![\n        (\"type\", \"u32\"),\n        (\"parent_id\", \"u32\"),\n        (\"name_checksum\", \"u16\"),\n    ];\n\n    // Allowed object types\n    let allowed_types: Vec<usize> = vec![0, 1, 2, 3, 4, 5];\n\n    // Parse the object header\n    if let Ok(obj_header) = common::parse(header_data, &yaffs_object_structure, endianness) {\n        // Validate that the header looks sane\n        if allowed_types.contains(&obj_header[\"type\"])\n            && (obj_header[\"parent_id\"] > 0)\n            && (obj_header[\"name_checksum\"] == UNUSED)\n        {\n            return Ok(YAFFSObject {\n                obj_type: obj_header[\"type\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a YAFFS file header\n#[derive(Debug, Default, Clone)]\npub struct YAFFSFileHeader {\n    // Only this field is needed, for now. Struct may be updated in the future if necessary.\n    pub file_size: usize,\n}\n\n/// Partially parse a YAFFS file header\npub fn parse_yaffs_file_header(\n    header_data: &[u8],\n    endianness: &str,\n) -> Result<YAFFSFileHeader, StructureError> {\n    // Second part of an object header (after the name field)\n    let yaffs_file_info = vec![\n        (\"mode\", \"u32\"),\n        (\"uid\", \"u32\"),\n        (\"gid\", \"u32\"),\n        (\"atime\", \"u32\"),\n        (\"mtime\", \"u32\"),\n        (\"ctime\", \"u32\"),\n        (\"file_size\", \"u32\"),\n    ];\n\n    if let Ok(file_info) = common::parse(header_data, &yaffs_file_info, endianness) {\n        return Ok(YAFFSFileHeader {\n            file_size: file_info[\"file_size\"],\n        });\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/zip.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n#[derive(Debug, Default, Clone)]\npub struct ZipFileHeader {\n    pub data_size: usize,\n    pub header_size: usize,\n    pub total_size: usize,\n    pub version_major: usize,\n    pub version_minor: usize,\n}\n\n/// Validate a ZIP file header\npub fn parse_zip_header(zip_data: &[u8]) -> Result<ZipFileHeader, StructureError> {\n    // Unused flag bits\n    const UNUSED_FLAGS_MASK: usize = 0b11010111_10000000;\n\n    // Encrypted compression type\n    const COMPRESSION_ENCRYPTED: usize = 99;\n\n    let zip_local_file_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"version\", \"u16\"),\n        (\"flags\", \"u16\"),\n        (\"compression\", \"u16\"),\n        (\"modification_time\", \"u16\"),\n        (\"modification_date\", \"u16\"),\n        (\"crc\", \"u32\"),\n        (\"compressed_size\", \"u32\"),\n        (\"uncompressed_size\", \"u32\"),\n        (\"file_name_len\", \"u16\"),\n        (\"extra_field_len\", \"u16\"),\n    ];\n\n    let allowed_compression_methods: Vec<usize> = vec![\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        8,\n        9,\n        10,\n        12,\n        14,\n        18,\n        19,\n        20,\n        93,\n        94,\n        95,\n        96,\n        97,\n        98,\n        COMPRESSION_ENCRYPTED,\n    ];\n\n    let mut result = ZipFileHeader {\n        ..Default::default()\n    };\n\n    // Parse the ZIP local file structure\n    if let Ok(zip_local_file_header) = common::parse(zip_data, &zip_local_file_structure, \"little\")\n    {\n        // Unused/reserved flag bits should be 0\n        if (zip_local_file_header[\"flags\"] & UNUSED_FLAGS_MASK) == 0 {\n            // Specified compression method should be one of the defined ZIP compression methods\n            if allowed_compression_methods.contains(&zip_local_file_header[\"compression\"]) {\n                result.version_major = zip_local_file_header[\"version\"] / 10;\n                result.version_minor = zip_local_file_header[\"version\"] % 10;\n                result.header_size = common::size(&zip_local_file_structure)\n                    + zip_local_file_header[\"file_name_len\"]\n                    + zip_local_file_header[\"extra_field_len\"];\n                result.data_size = if zip_local_file_header[\"compressed_size\"] > 0 {\n                    zip_local_file_header[\"compressed_size\"]\n                } else {\n                    zip_local_file_header[\"uncompressed_size\"]\n                };\n                result.total_size = result.header_size + result.data_size;\n                return Ok(result);\n            }\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a ZIP end-of-central-directory header\n#[derive(Debug, Default, Clone)]\npub struct ZipEOCDHeader {\n    pub size: usize,\n    pub file_count: usize,\n}\n\n/// Parse a ZIP end-of-central-directory header\npub fn parse_eocd_header(eocd_data: &[u8]) -> Result<ZipEOCDHeader, StructureError> {\n    let zip_eocd_structure = vec![\n        (\"magic\", \"u32\"),\n        (\"disk_number\", \"u16\"),\n        (\"central_directory_disk_number\", \"u16\"),\n        (\"central_directory_disk_entries\", \"u16\"),\n        (\"central_directory_total_entries\", \"u16\"),\n        (\"central_directory_size\", \"u32\"),\n        (\"central_directory_offset\", \"u32\"),\n        (\"comment_length\", \"u16\"),\n    ];\n\n    // Parse the EOCD header\n    if let Ok(zip_eocd_header) = common::parse(eocd_data, &zip_eocd_structure, \"little\") {\n        // Assume there is only one \"disk\", so disk entries and total entries should be the same, and the ZIP archive should contain at least one file\n        if zip_eocd_header[\"central_directory_disk_entries\"]\n            == zip_eocd_header[\"central_directory_total_entries\"]\n            && zip_eocd_header[\"central_directory_total_entries\"] > 0\n        {\n            // An optional comment may follow the EOCD header; include the comment length in the offset of the ZIP EOF offset\n            let zip_eof: usize =\n                common::size(&zip_eocd_structure) + zip_eocd_header[\"comment_length\"];\n\n            return Ok(ZipEOCDHeader {\n                size: zip_eof,\n                file_count: zip_eocd_header[\"central_directory_total_entries\"],\n            });\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures/zstd.rs",
    "content": "use crate::structures::common::{self, StructureError};\n\n/// Stores info about a ZSTD file header\n#[derive(Debug, Default, Clone)]\npub struct ZSTDHeader {\n    pub fixed_header_size: usize,\n    pub dictionary_id_flag: usize,\n    pub content_checksum_present: bool,\n    pub single_segment_flag: bool,\n    pub frame_content_flag: usize,\n}\n\n/// Parse a ZSTD file header\npub fn parse_zstd_header(zstd_data: &[u8]) -> Result<ZSTDHeader, StructureError> {\n    // Mask and shift bits\n    const FRAME_UNUSED_BITS_MASK: usize = 0b00011000;\n    const DICTIONARY_ID_MASK: usize = 0b11;\n    const CONTENT_CHECKSUM_MASK: usize = 0b100;\n    const SINGLE_SEGMENT_MASK: usize = 0b100000;\n    const FRAME_CONTENT_MASK: usize = 0b11000000;\n    const FRAME_CONTENT_SHIFT: usize = 6;\n\n    let zstd_header_structure = vec![(\"magic\", \"u32\"), (\"frame_header_descriptor\", \"u8\")];\n\n    let mut zstd_info = ZSTDHeader {\n        fixed_header_size: common::size(&zstd_header_structure),\n        ..Default::default()\n    };\n\n    // Parse the ZSTD header\n    if let Ok(zstd_header) = common::parse(zstd_data, &zstd_header_structure, \"little\") {\n        // Unused bits should be unused\n        if (zstd_header[\"frame_header_descriptor\"] & FRAME_UNUSED_BITS_MASK) == 0 {\n            // Indicates if a dictionary ID field is present, and if so, how big it is\n            zstd_info.dictionary_id_flag =\n                zstd_header[\"frame_header_descriptor\"] & DICTIONARY_ID_MASK;\n\n            // Indicates if there is a 4-byte checksum present at the end of the compressed block stream\n            zstd_info.content_checksum_present =\n                (zstd_header[\"frame_header_descriptor\"] & CONTENT_CHECKSUM_MASK) != 0;\n\n            // If this flag is set, then the window descriptor byte is not present\n            zstd_info.single_segment_flag =\n                (zstd_header[\"frame_header_descriptor\"] & SINGLE_SEGMENT_MASK) != 0;\n\n            // Indicates if the frame content field is present, and if so, how big it is\n            zstd_info.frame_content_flag = (zstd_header[\"frame_header_descriptor\"]\n                & FRAME_CONTENT_MASK)\n                >> FRAME_CONTENT_SHIFT;\n\n            return Ok(zstd_info);\n        }\n    }\n\n    Err(StructureError)\n}\n\n/// Stores info about a ZSTD block header\n#[derive(Debug, Default, Clone)]\npub struct ZSTDBlockHeader {\n    pub header_size: usize,\n    pub block_type: usize,\n    pub block_size: usize,\n    pub last_block: bool,\n}\n\n/// Parse a ZSTD block header\npub fn parse_block_header(block_data: &[u8]) -> Result<ZSTDBlockHeader, StructureError> {\n    // Bit mask constants\n    const ZSTD_BLOCK_TYPE_MASK: usize = 0b110;\n    const ZSTD_BLOCK_TYPE_SHIFT: usize = 1;\n    const ZSTD_RLE_BLOCK_TYPE: usize = 1;\n    const ZSTD_RESERVED_BLOCK_TYPE: usize = 3;\n    const ZSTD_LAST_BLOCK_MASK: usize = 0b1;\n    const ZSTD_BLOCK_SIZE_MASK: usize = 0b1111_1111_1111_1111_1111_1000;\n    const ZSTD_BLOCK_SIZE_SHIFT: usize = 3;\n\n    let zstd_block_header_structure = vec![(\"info_bits\", \"u24\")];\n\n    let mut block_info = ZSTDBlockHeader {\n        header_size: common::size(&zstd_block_header_structure),\n        ..Default::default()\n    };\n\n    // Parse the block header\n    if let Ok(block_header) = common::parse(block_data, &zstd_block_header_structure, \"little\") {\n        // Interpret the bit fields of the block header, which indicate the type of block, the size of the block, and if this is the last block\n        block_info.last_block = (block_header[\"info_bits\"] & ZSTD_LAST_BLOCK_MASK) != 0;\n        block_info.block_type =\n            (block_header[\"info_bits\"] & ZSTD_BLOCK_TYPE_MASK) >> ZSTD_BLOCK_TYPE_SHIFT;\n        block_info.block_size =\n            (block_header[\"info_bits\"] & ZSTD_BLOCK_SIZE_MASK) >> ZSTD_BLOCK_SIZE_SHIFT;\n\n        /*\n         * An RLE block consists of a single byte of raw block data, which when decompressed must be repeased block_size times.\n         * We're not decompressing, just want to know the size of the raw data so we can check the next block header.\n         *\n         * Reserved block types should not be encountered, and are considered an error during decompression.\n         */\n        if block_info.block_type == ZSTD_RLE_BLOCK_TYPE {\n            block_info.block_size = 1;\n        }\n\n        // Block type is invalid if set to the reserved block type\n        if block_info.block_type != ZSTD_RESERVED_BLOCK_TYPE {\n            return Ok(block_info);\n        }\n    }\n\n    Err(StructureError)\n}\n"
  },
  {
    "path": "src/structures.rs",
    "content": "//! # Data Structure Parsing\n//!\n//! Both signatures and internal extractors may need to parse data structures used by various file formats.\n//! Structure parsing code is placed in the `structures` module.\n//!\n//! ## Helper Functions\n//!\n//! There are some definitions and helper functions in `structures::common` that are generally helpful for processing data structures.\n//!\n//! The `structures::common::parse` function provides a way to parse basic data structures by defining the data structure format,\n//! the endianness to use, and the data to cast the structure over. It is heavily used by most structure parsers.\n//! It supports the following data types:\n//!\n//! - u8\n//! - u16\n//! - u24\n//! - u32\n//! - u64\n//!\n//! Regardless of the data type specified, all values are returned as `usize` types.\n//! If an error occurs (typically due to not enough data available to process the specified data structure), `Err(structures::common::StructureError)` is returned.\n//!\n//! The `structures::common::size` function is a convenience function that returns the number of bytes required to parse a defined data structure.\n//!\n//! The `structures::common::StructureError` struct is typically used by structure parsers to return an error.\n//!\n//! ## Writing a Structure Parser\n//!\n//! Structure parsers may be defined however they need to be; there are no strict rules here.\n//! Generally, however, they should:\n//!\n//! - Accept some data to parse\n//! - Parse the data structure\n//! - Validate the structure fields for correctness\n//! - Return an error or success status\n//!\n//! ### Example\n//!\n//! To write a structure parser for a simple, fictional, `FooBar` file header:\n//!\n//! ```no_run\n//! use binwalk::common::{crc32, get_cstring};\n//! use binwalk::structures::common::{self, StructureError};\n//!\n//! #[derive(Debug, Default, Clone)]\n//! pub struct FooBarHeader {\n//!     pub data_crc: usize,\n//!     pub data_size: usize,\n//!     pub header_size: usize,\n//!     pub original_file_name: String,\n//! }\n//!\n//! /// This function parses and validates the FooBar file header.\n//! /// It returns a FooBarHeader struct on success, StructureError on failure.\n//! fn parse_foobar_header(foobar_data: &[u8]) -> Result<FooBarHeader, StructureError> {\n//!     // The header CRC is calculated over the first 13 bytes of the header (everything except the header_crc field)\n//!     const HEADER_CRC_LEN: usize = 13;\n//!\n//!     // Define a data structure; structure members must be in the order in which they appear in the data\n//!     let foobar_struct = vec![\n//!         (\"magic\", \"u32\"),\n//!         (\"flags\", \"u8\"),\n//!         (\"data_size\", \"u32\"),\n//!         (\"data_crc\", \"u32\"),\n//!         (\"header_crc\", \"u32\"),\n//!         // NULL-terminated original file name immediately follows the header structure\n//!     ];\n//!\n//!     let struct_size: usize = common::size(&foobar_struct);\n//!\n//!     // Parse the provided data in accordance with the layout defined in foobar_struct, interpret fields as little endian\n//!     if let Ok(foobar_header) = common::parse(foobar_data, &foobar_struct, \"little\") {\n//!         \n//!         if let Some(crc_data) = foobar_data.get(0..HEADER_CRC_LEN) {\n//!             // Validate the header CRC\n//!             if foobar_header[\"header_crc\"] == (crc32(crc_data) as usize) {\n//!                 // Get the NULL-terminated file name that immediately follows the header structure\n//!                 if let Some(file_name_bytes) = foobar_data.get(struct_size..) {\n//!                     let file_name = get_cstring(file_name_bytes);\n//!\n//!                     // The file name should be non-zero in length\n//!                     if file_name.len() > 0 {\n//!                         return Ok(FooBarHeader{\n//!                             data_crc: foobar_header[\"data_crc\"],\n//!                             data_size: foobar_header[\"data_size\"],\n//!                             header_size: struct_size + file_name.len() + 1,  // Total header size is structure size + name length + NULL byte\n//!                             original_file_name: file_name.clone(),\n//!                         });\n//!                     }\n//!                 }\n//!             }\n//!         }\n//!     }\n//!\n//!     return Err(StructureError);\n//! }\n//! ```\n\npub mod android_bootimg;\npub mod androidsparse;\npub mod apfs;\npub mod arj;\npub mod autel;\npub mod binhdr;\npub mod bmp;\npub mod btrfs;\npub mod cab;\npub mod chk;\npub mod common;\npub mod cpio;\npub mod cramfs;\npub mod csman;\npub mod deb;\npub mod dkbs;\npub mod dlink_tlv;\npub mod dlob;\npub mod dmg;\npub mod dms;\npub mod dpapi;\npub mod dtb;\npub mod dxbc;\npub mod efigpt;\npub mod elf;\npub mod ext;\npub mod fat;\npub mod gif;\npub mod gzip;\npub mod iso9660;\npub mod jboot;\npub mod jffs2;\npub mod linux;\npub mod logfs;\npub mod luks;\npub mod lz4;\npub mod lzfse;\npub mod lzma;\npub mod lzop;\npub mod matter_ota;\npub mod mbr;\npub mod mh01;\npub mod ntfs;\npub mod openssl;\npub mod packimg;\npub mod pcap;\npub mod pchrom;\npub mod pe;\npub mod png;\npub mod qcow;\npub mod qnx;\npub mod rar;\npub mod riff;\npub mod romfs;\npub mod rtk;\npub mod seama;\npub mod sevenzip;\npub mod shrs;\npub mod squashfs;\npub mod svg;\npub mod tplink;\npub mod trx;\npub mod ubi;\npub mod uefi;\npub mod uimage;\npub mod vxworks;\npub mod wince;\npub mod xz;\npub mod yaffs;\npub mod zip;\npub mod zstd;\n"
  },
  {
    "path": "tests/arcadyan.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"arcadyan\";\n    const INPUT_FILE_NAME: &str = \"arcadyan.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/arj.rs",
    "content": "use crate::common::assert_results_ok;\n\nmod common;\n\n#[test]\nfn integration_test_valid_arj() {\n    const SIGNATURE_TYPE: &str = \"arj\";\n    const INPUT_FILE_NAME: &str = \"arj.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![0xD, 0x46];\n    let expected_extraction_offsets: Vec<usize> = vec![0xD];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n\n    assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    )\n}\n"
  },
  {
    "path": "tests/bmp.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"bmp\";\n    const INPUT_FILE_NAME: &str = \"bmp.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![0xB7F94, 0x10AFEC];\n    let expected_extraction_offsets: Vec<usize> = vec![0xB7F94, 0x10AFEC];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n    common::assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n"
  },
  {
    "path": "tests/bzip2.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"bzip2\";\n    const INPUT_FILE_NAME: &str = \"bzip2.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/common/mod.rs",
    "content": "use binwalk::{AnalysisResults, Binwalk};\n\n/// Convenience function for running an integration test against the specified file, with the provided signature filter.\n/// Assumes that there will be one signature result and one extraction result at file offset 0.\n#[allow(dead_code)]\npub fn integration_test(signature_filter: &str, file_name: &str) {\n    let expected_signature_offsets: Vec<usize> = vec![0];\n    let expected_extraction_offsets: Vec<usize> = vec![0];\n\n    // Run binwalk, get analysis/extraction results\n    let results = run_binwalk(signature_filter, file_name);\n\n    // Assert that there was a valid signature and successful result at, and only at, file offset 0\n    assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n\n/// Assert that there was a valid signature match and corresponding extraction at, and only at, the specified file offsets\npub fn assert_results_ok(\n    results: AnalysisResults,\n    signature_offsets: Vec<usize>,\n    extraction_offsets: Vec<usize>,\n) {\n    // Assert that the number of signature results and extractions match the expected results\n    assert!(results.file_map.len() == signature_offsets.len());\n    assert!(results.extractions.len() == extraction_offsets.len());\n\n    // Assert that each signature match was at an expected offset and that extraction, if expected, was successful\n    for signature_result in results.file_map {\n        assert!(signature_offsets.contains(&signature_result.offset));\n        if extraction_offsets.contains(&signature_result.offset) {\n            assert!(results.extractions[&signature_result.id].success);\n        }\n    }\n}\n\n/// Run Binwalk, with extraction, against the specified file, with the provided signature filter\npub fn run_binwalk(signature_filter: &str, file_name: &str) -> AnalysisResults {\n    // Build the path to the input file\n    let file_path = std::path::Path::new(\"tests\")\n        .join(\"inputs\")\n        .join(file_name)\n        .display()\n        .to_string();\n\n    // Build the path to the output directory\n    let output_directory = std::path::Path::new(\"tests\")\n        .join(\"binwalk_integration_test_extractions\")\n        .display()\n        .to_string();\n\n    // Delete the output directory, if it exists\n    let _ = std::fs::remove_dir_all(&output_directory);\n\n    // Configure binwalk\n    let binwalker = Binwalk::configure(\n        Some(file_path),\n        Some(output_directory.clone()),\n        Some(vec![signature_filter.to_string()]),\n        None,\n        None,\n        false,\n    )\n    .expect(\"Binwalk initialization failed\");\n\n    // Run analysis\n    let results = binwalker.analyze(&binwalker.base_target_file, true);\n\n    // Clean up the output directory\n    let _ = std::fs::remove_dir_all(output_directory);\n\n    results\n}\n"
  },
  {
    "path": "tests/cramfs.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"cramfs\";\n    const INPUT_FILE_NAME: &str = \"cramfs.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/gzip.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"gzip\";\n    const INPUT_FILE_NAME: &str = \"gzip.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/jpeg.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"jpeg\";\n    const INPUT_FILE_NAME: &str = \"jpeg.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![0, 0x15BBE];\n    let expected_extraction_offsets: Vec<usize> = vec![0, 0x15BBE];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n    common::assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n"
  },
  {
    "path": "tests/matter_ota.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"matter_ota\";\n    const INPUT_FILE_NAME: &str = \"matter_ota.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/mbr.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"mbr\";\n    const INPUT_FILE_NAME: &str = \"mbr.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/pdf.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"pdf\";\n    const INPUT_FILE_NAME: &str = \"pdf.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![0];\n    let expected_extraction_offsets: Vec<usize> = vec![];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n    common::assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n"
  },
  {
    "path": "tests/png.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"png\";\n    const INPUT_FILE_NAME: &str = \"png_malformed.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![];\n    let expected_extraction_offsets: Vec<usize> = vec![];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n    common::assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n"
  },
  {
    "path": "tests/qcow.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"qcow\";\n    const INPUT_FILE_NAME: &str = \"qcow.bin\";\n\n    let expected_signature_offsets: Vec<usize> = vec![0];\n    let expected_extraction_offsets: Vec<usize> = vec![];\n\n    let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);\n    common::assert_results_ok(\n        results,\n        expected_signature_offsets,\n        expected_extraction_offsets,\n    );\n}\n"
  },
  {
    "path": "tests/riff.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"riff\";\n    const INPUT_FILE_NAME: &str = \"riff.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/romfs.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"romfs\";\n    const INPUT_FILE_NAME: &str = \"romfs.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/sevenzip.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"7zip\";\n    const INPUT_FILE_NAME: &str = \"7z.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/squashfs.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"squashfs\";\n    const INPUT_FILE_NAME: &str = \"squashfs.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/squashfs_v2.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"squashfs\";\n    const INPUT_FILE_NAME: &str = \"squashfs_v2.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/yaffs2.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test() {\n    const SIGNATURE_TYPE: &str = \"yaffs\";\n    const INPUT_FILE_NAME: &str = \"yaffs2.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/zip.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test_valid_zip() {\n    const SIGNATURE_TYPE: &str = \"zip\";\n    const INPUT_FILE_NAME: &str = \"zip.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  },
  {
    "path": "tests/zip_truncated.rs",
    "content": "mod common;\n\n#[test]\nfn integration_test_truncated_zip() {\n    const SIGNATURE_TYPE: &str = \"zip\";\n    const INPUT_FILE_NAME: &str = \"zip_truncated.bin\";\n    common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);\n}\n"
  }
]