ic-host-wasm-inspect 0.26.2

Bounded structural Wasm inspection CLI
//! Inspect explicit raw Wasm inputs without executing or transforming them.

use std::{env, fmt, io, io::Write, num::ParseIntError, process::ExitCode};

use ic_host_artifacts::{
    artifact::{ArtifactError, DigestParseError},
    wasm::InspectionError,
};

mod args;
mod report;

// The executable owns failures across argument parsing, input and reporting.
#[derive(Debug)]
enum Error {
    MissingArgument(&'static str),
    ExtraArgument,
    NonUtf8Number(&'static str),
    NonUtf8Digest,
    InvalidDigest(DigestParseError),
    InvalidNumber {
        name: &'static str,
        source: ParseIntError,
    },
    Input(ArtifactError),
    Inspection(InspectionError),
    Output(io::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MissingArgument(name) => {
                write!(formatter, "missing {name}; {}", args::USAGE)
            }
            Self::ExtraArgument => write!(formatter, "unexpected argument; {}", args::USAGE),
            Self::NonUtf8Number(name) => {
                write!(formatter, "{name} must be a UTF-8 unsigned integer")
            }
            Self::NonUtf8Digest => {
                formatter.write_str("SHA256 must be UTF-8 lowercase hexadecimal")
            }
            Self::InvalidDigest(source) => write!(formatter, "invalid SHA256: {source}"),
            Self::InvalidNumber { name, source } => write!(formatter, "invalid {name}: {source}"),
            Self::Input(source) => write!(formatter, "input admission failed: {source}"),
            Self::Inspection(source) => write!(formatter, "Wasm inspection failed: {source}"),
            Self::Output(source) => write!(formatter, "report output failed: {source}"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::InvalidNumber { source, .. } => Some(source),
            Self::InvalidDigest(source) => Some(source),
            Self::Input(source) => Some(source),
            Self::Inspection(source) => Some(source),
            Self::Output(source) => Some(source),
            Self::MissingArgument(_)
            | Self::ExtraArgument
            | Self::NonUtf8Number(_)
            | Self::NonUtf8Digest => None,
        }
    }
}

fn main() -> ExitCode {
    let result = args::parse(env::args_os().skip(1)).and_then(|command| match command {
        args::Command::Help => {
            writeln!(io::stdout().lock(), "{}", args::USAGE).map_err(Error::Output)
        }
        args::Command::Inspect {
            path,
            limits,
            expected,
            details,
        } => {
            let bytes =
                ic_host_fs::read::read_file(&path, limits.module_bytes).map_err(Error::Input)?;
            report::write(&bytes, limits, expected, details, &mut io::stdout().lock())
        }
    });
    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            let _ = writeln!(io::stderr().lock(), "{error}");
            ExitCode::FAILURE
        }
    }
}