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);
}