oliphaunt-extension-pg-hashids-linux-x64-gnu 0.1.0

Cargo artifact crate for the pg_hashids Oliphaunt native extension carrier on linux-x64-gnu.
Documentation
use std::env;
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};

const SCHEMA: &str = "oliphaunt-artifact-manifest-v1";
const PRODUCT: &str = "oliphaunt-extension-pg-hashids";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const KIND: &str = "extension";
const TARGET: &str = "x86_64-unknown-linux-gnu";
const RUNTIME_PRODUCT: &str = "liboliphaunt-native";
const RUNTIME_VERSION: &str = "0.1.0";
const EXTENSIONS: &[&str] = &["pg_hashids"];
const EXTENSION_DEPENDENCIES: &[(&str, &[&str])] = &[("pg_hashids", &[])];

fn main() {
    let manifest_dir =
        PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"));
    let payload = manifest_dir.join("payload");
    println!("cargo::rerun-if-changed={}", payload.display());
    if !payload.is_dir() {
        if env::var_os("OLIPHAUNT_ARTIFACT_CRATE_REQUIRE_PAYLOAD").is_some() {
            panic!("missing packaged extension payload under {}", payload.display());
        }
        return;
    }
    let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set"));
    let manifest = out_dir.join("oliphaunt-artifact.toml");
    let mut text = format!(
        "schema = {SCHEMA:?}\nproduct = {PRODUCT:?}\nversion = {VERSION:?}\nkind = {KIND:?}\ntarget = {TARGET:?}\nruntime-product = {RUNTIME_PRODUCT:?}\nruntime-version = {RUNTIME_VERSION:?}\n"
    );
    if SCHEMA == "oliphaunt-artifact-manifest-v1" {
        if EXTENSIONS.len() != 1 { panic!("v1 extension manifest requires exactly one member"); }
        text.push_str(&format!("extension = {:?}\n", EXTENSIONS[0]));
        append_dependencies(&mut text, EXTENSIONS[0]);
        append_manifest_files(&mut text, &payload, "[[files]]");
    } else {
        let extensions_root = payload.join("extensions");
        let mut actual_members: Vec<String> = fs::read_dir(&extensions_root)
            .expect("read extension bundle members")
            .filter_map(|entry| entry.ok())
            .filter(|entry| entry.file_type().map(|kind| kind.is_dir()).unwrap_or(false))
            .map(|entry| entry.file_name().to_string_lossy().into_owned())
            .collect();
        actual_members.sort();
        let expected_members: Vec<String> = EXTENSIONS.iter().map(|value| (*value).to_owned()).collect();
        if actual_members != expected_members {
            panic!("extension bundle member set mismatch: expected {expected_members:?}, got {actual_members:?}");
        }
        for extension in EXTENSIONS {
            text.push_str(&format!("\n[[extensions]]\nextension = {extension:?}\n"));
            append_dependencies(&mut text, extension);
            append_manifest_files(&mut text, &extensions_root.join(extension), "[[extensions.files]]");
        }
    }
    fs::write(&manifest, text).expect("write Oliphaunt extension artifact manifest");
    println!("cargo::metadata=manifest={}", manifest.display());
}

fn append_dependencies(text: &mut String, extension: &str) {
    let dependencies = EXTENSION_DEPENDENCIES.iter()
        .find(|(candidate, _)| *candidate == extension)
        .map(|(_, dependencies)| *dependencies)
        .unwrap_or_else(|| panic!("missing dependency metadata for extension {extension}"));
    text.push_str(&format!("dependencies = {dependencies:?}\n"));
}

fn append_manifest_files(text: &mut String, root: &Path, table: &str) {
    let files = payload_files(root);
    if files.is_empty() { panic!("empty extension payload under {}", root.display()); }
    for file in files {
        let relative = file
            .strip_prefix(root)
            .expect("payload file stays under member root")
            .to_string_lossy()
            .replace(std::path::MAIN_SEPARATOR, "/");
        let sha256 = sha256_file(&file).expect("hash payload file");
        text.push_str(&format!(
            "\n{table}\nsource = {:?}\nrelative = {:?}\nsha256 = {sha256:?}\nexecutable = false\n",
            file.display().to_string(),
            relative,
        ));
    }
}

fn payload_files(root: &Path) -> Vec<PathBuf> {
    let mut files = Vec::new();
    collect_payload_files(root, &mut files);
    files.sort();
    files
}

fn collect_payload_files(root: &Path, files: &mut Vec<PathBuf>) {
    for entry in fs::read_dir(root).expect("read payload directory") {
        let path = entry.expect("read payload entry").path();
        if path.is_dir() {
            collect_payload_files(&path, files);
        } else if path.is_file() {
            files.push(path);
        }
    }
}


fn sha256_file(path: &Path) -> io::Result<String> {
    let mut file = fs::File::open(path)?;
    let mut state = [
        0x6a09e667_u32,
        0xbb67ae85,
        0x3c6ef372,
        0xa54ff53a,
        0x510e527f,
        0x9b05688c,
        0x1f83d9ab,
        0x5be0cd19,
    ];
    let mut pending = [0_u8; 64];
    let mut pending_len = 0_usize;
    let mut total_len = 0_u64;
    let mut input = [0_u8; 64 * 1024];

    loop {
        let read = file.read(&mut input)?;
        if read == 0 {
            break;
        }
        total_len = total_len.wrapping_add(read as u64);
        let mut offset = 0_usize;
        if pending_len != 0 {
            let copied = (64 - pending_len).min(read);
            pending[pending_len..pending_len + copied].copy_from_slice(&input[..copied]);
            pending_len += copied;
            offset += copied;
            if pending_len == 64 {
                sha256_compress(&mut state, &pending);
                pending_len = 0;
            }
        }
        while offset + 64 <= read {
            sha256_compress(&mut state, &input[offset..offset + 64]);
            offset += 64;
        }
        if offset != read {
            pending[..read - offset].copy_from_slice(&input[offset..read]);
            pending_len = read - offset;
        }
    }

    pending[pending_len] = 0x80;
    pending_len += 1;
    if pending_len > 56 {
        pending[pending_len..].fill(0);
        sha256_compress(&mut state, &pending);
        pending.fill(0);
    } else {
        pending[pending_len..56].fill(0);
    }
    pending[56..].copy_from_slice(&total_len.wrapping_mul(8).to_be_bytes());
    sha256_compress(&mut state, &pending);

    let mut output = String::with_capacity(64);
    for word in state {
        use std::fmt::Write as _;
        write!(&mut output, "{word:08x}").expect("write SHA-256 hex digest");
    }
    Ok(output)
}

fn sha256_compress(state: &mut [u32; 8], block: &[u8]) {
    const K: [u32; 64] = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
        0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
        0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
        0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
    ];
    let mut schedule = [0_u32; 64];
    for (index, bytes) in block.chunks_exact(4).take(16).enumerate() {
        schedule[index] = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
    }
    for index in 16..64 {
        let s0 = schedule[index - 15].rotate_right(7)
            ^ schedule[index - 15].rotate_right(18)
            ^ (schedule[index - 15] >> 3);
        let s1 = schedule[index - 2].rotate_right(17)
            ^ schedule[index - 2].rotate_right(19)
            ^ (schedule[index - 2] >> 10);
        schedule[index] = schedule[index - 16]
            .wrapping_add(s0)
            .wrapping_add(schedule[index - 7])
            .wrapping_add(s1);
    }

    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
    for index in 0..64 {
        let sum1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
        let choose = (e & f) ^ ((!e) & g);
        let temporary1 = h
            .wrapping_add(sum1)
            .wrapping_add(choose)
            .wrapping_add(K[index])
            .wrapping_add(schedule[index]);
        let sum0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
        let majority = (a & b) ^ (a & c) ^ (b & c);
        let temporary2 = sum0.wrapping_add(majority);
        h = g;
        g = f;
        f = e;
        e = d.wrapping_add(temporary1);
        d = c;
        c = b;
        b = a;
        a = temporary1.wrapping_add(temporary2);
    }
    state[0] = state[0].wrapping_add(a);
    state[1] = state[1].wrapping_add(b);
    state[2] = state[2].wrapping_add(c);
    state[3] = state[3].wrapping_add(d);
    state[4] = state[4].wrapping_add(e);
    state[5] = state[5].wrapping_add(f);
    state[6] = state[6].wrapping_add(g);
    state[7] = state[7].wrapping_add(h);
}