#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::process::Command;
use znippy_common::arrow::ipc::{MessageHeader, root_as_message};
pub const CASE_ENV: &str = "GUNGUARD_HOSTILE_CASE";
pub fn child_case() -> Option<String> {
std::env::var(CASE_ENV).ok()
}
pub fn run_in_child(child_test: &str, case: &str) {
let exe = std::env::current_exe().unwrap();
let out = Command::new(exe)
.args([
child_test,
"--exact",
"--ignored",
"--nocapture",
"--test-threads=1",
])
.env(CASE_ENV, case)
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
out.status.success() && !stderr.contains("panicked") && stdout.contains("HOSTILE-DONE"),
"hostile case {case}: the child did not end cleanly ({:?})\n--- stdout\n{}\n--- stderr\n{}",
out.status,
tail(&stdout),
tail(&stderr)
);
}
fn tail(s: &str) -> &str {
let n = s.len();
let mut at = n.saturating_sub(4000);
while !s.is_char_boundary(at) {
at += 1;
}
&s[at..]
}
pub fn fixture_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../tests/fixtures/hostile-ipc")
}
pub fn seed_streams() -> Vec<(String, Vec<u8>)> {
let mut out = Vec::new();
let mut names: Vec<_> = std::fs::read_dir(fixture_dir())
.expect("znippy tests/fixtures/hostile-ipc")
.map(|e| e.unwrap().path())
.collect();
names.sort();
for p in names {
let bytes = std::fs::read(&p).unwrap();
let name = p.file_name().unwrap().to_string_lossy().into_owned();
for at in schema_starts(&bytes) {
out.push((format!("{name}@{at}"), bytes[at..].to_vec()));
}
out.push((format!("{name}:whole"), bytes));
}
assert!(out.len() > 6, "no IPC stream found in the seeds");
out
}
fn schema_starts(b: &[u8]) -> Vec<usize> {
let mut v = Vec::new();
let mut i = 0;
while i + 8 <= b.len() {
if b[i..i + 4] == [0xFF; 4] {
let len = u32::from_le_bytes(b[i + 4..i + 8].try_into().unwrap()) as usize;
if (8..=1 << 20).contains(&len)
&& i + 8 + len <= b.len()
&& root_as_message(&b[i + 8..i + 8 + len])
.is_ok_and(|m| m.header_type() == MessageHeader::Schema)
{
v.push(i);
}
}
i += 1;
}
v
}
pub fn messages(b: &[u8]) -> Vec<(usize, usize, i64)> {
let mut out = Vec::new();
let mut pos = 0usize;
while pos + 8 <= b.len() {
let mut len = u32::from_le_bytes(b[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
if len == 0xFFFF_FFFF {
len = u32::from_le_bytes(b[pos..pos + 4].try_into().unwrap()) as usize;
pos += 4;
}
if len == 0 {
continue; }
if pos + len > b.len() {
break;
}
let Ok(m) = root_as_message(&b[pos..pos + len]) else {
break;
};
let body = m.bodyLength();
out.push((pos, len, body));
if body < 0 || pos + len + body as usize > b.len() {
break;
}
pos += len + body as usize;
}
out
}
pub fn body_length_variants(valid: &[u8], value: i64) -> Vec<Vec<u8>> {
let mut out = Vec::new();
for (at, len, body) in messages(valid) {
let meta = &valid[at..at + len];
if root_as_message(meta).unwrap().header_type() != MessageHeader::RecordBatch {
continue;
}
let Some(p) = meta.windows(8).position(|w| w == body.to_le_bytes()) else {
continue;
};
let mut m = valid.to_vec();
m[at + p..at + p + 8].copy_from_slice(&value.to_le_bytes());
out.push(m);
}
out
}
pub fn sweep(valid: &[u8], mut feed: impl FnMut(&str, &[u8])) -> usize {
let mut n = 0usize;
let mut run = |what: &str, b: &[u8]| {
feed(what, b);
n += 1;
};
run("valid", valid);
for v in [1i64 << 40, -8, i64::MAX] {
let vs = body_length_variants(valid, v);
assert!(!vs.is_empty(), "the valid input carries no record batch");
for m in vs {
run(&format!("bodyLength={v}"), &m);
}
}
for (at, len, _) in messages(valid) {
let meta = &valid[at..at + len];
if root_as_message(meta).unwrap().header_type() != MessageHeader::RecordBatch {
continue;
}
for w in (0..len.saturating_sub(7)).step_by(4) {
for v in [(1i64 << 31) - 1, 1i64 << 40] {
let mut m = valid.to_vec();
m[at + w..at + w + 8].copy_from_slice(&v.to_le_bytes());
run("huge word", &m);
}
}
}
for (name, s) in seed_streams() {
run(&name, &s);
}
for cut in 0..valid.len() {
run("truncated", &valid[..cut]);
}
for i in 0..valid.len() {
for v in [0x00u8, 0xFF, 0x7F, 0x80, valid[i] ^ 0x01] {
if v == valid[i] {
continue;
}
let mut m = valid.to_vec();
m[i] = v;
run("flip", &m);
}
}
println!("HOSTILE-DONE {n} inputs");
n
}