use super::*; use std::cell::Cell; fn header(t: Target) -> Vec { let mut b = vec![0; 256]; match t { Target::WindowsAmd64 => { b[..2].copy_from_slice(b"MZ"); b[0x3c..0x40].copy_from_slice(&64u32.to_le_bytes()); b[64..68].copy_from_slice(b"PE\0\0"); b[68..70].copy_from_slice(&0x8664u16.to_le_bytes()); b[86..88].copy_from_slice(&2u16.to_le_bytes()); b[88..90].copy_from_slice(&0x20bu16.to_le_bytes()); b[156..158].copy_from_slice(&3u16.to_le_bytes()); } Target::MacosArm64 => { b[..4].copy_from_slice(&[0xcf, 0xfa, 0xed, 0xfe]); b[4..8].copy_from_slice(&0x0100000cu32.to_le_bytes()); b[12..16].copy_from_slice(&2u32.to_le_bytes()); } Target::LinuxAmd64 | Target::LinuxArm64 => { b[..7].copy_from_slice(b"\x7fELF\x02\x01\x01"); b[16..18].copy_from_slice(&2u16.to_le_bytes()); let cpu: u16 = if t == Target::LinuxAmd64 { 62 } else { 183 }; b[18..20].copy_from_slice(&cpu.to_le_bytes()); } } b.extend_from_slice(&runtime_marker(t)); b } struct Dir(PathBuf); impl Dir { fn new() -> Self { let p = std::env::temp_dir().join(format!( "tb-export-test-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed) )); fs::create_dir(&p).unwrap(); Self(p) } } impl Drop for Dir { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } #[test] fn targets_versions_integrity_and_payload_bounds_are_enforced() { let d = Dir::new(); for t in Target::ALL { let b = header(t); assert_eq!(native_target(&b).unwrap(), t); let path = d.0.join(t.runtime_name()); fs::write(&path, &b).unwrap(); prepare_template(&path, t).unwrap(); assert_eq!(template(&path, t).unwrap(), b); let other = if t == Target::LinuxAmd64 { Target::LinuxArm64 } else { Target::LinuxAmd64 }; assert!(template(&path, other) .unwrap_err() .to_string() .contains("Architektur")); let meta = manifest(&b, t); for wrong in [ meta.replace("runtime=1", "runtime=2"), meta.replace("package=0.1.0", "package=9"), meta.replace("tbc=4", "tbc=99"), meta.replace("checksum=", "checksum=0"), ] { fs::write(manifest_path(&path), wrong).unwrap(); assert!(template(&path, t).is_err()); } fs::remove_file(manifest_path(&path)).unwrap(); assert!(template(&path, t).is_err()); fs::remove_file(&path).unwrap(); assert!(template(&path, t) .unwrap_err() .to_string() .contains("fehlt")); for len in 0..64 { assert!(native_target(&b[..len]).is_err()); } } assert!(Target::parse("aarch64-pc-windows-msvc").is_err()); assert!(Target::parse("x86_64-apple-darwin").is_err()); let mut foreign_abi = header(Target::LinuxAmd64); foreign_abi[7] = 9; assert!(native_target(&foreign_abi).is_err()); let module = tb_vm::compile_source("TEST", "PRINT 42\nEND\n").unwrap(); for t in [Target::WindowsAmd64, Target::LinuxAmd64, Target::LinuxArm64] { let mut b = header(t); if t == Target::WindowsAmd64 { let mut signed = b.clone(); signed[196..200].copy_from_slice(&5u32.to_le_bytes()); signed[232] = 1; assert!(append_payload(&mut signed, &module, t) .unwrap_err() .to_string() .contains("Signierte PE")); b[152..156].copy_from_slice(&123u32.to_le_bytes()); } append_payload(&mut b, &module, t).unwrap(); if t == Target::WindowsAmd64 { assert_eq!(&b[152..156], &[0; 4]); } assert_eq!(embedded(&b, t).unwrap().to_tbc(), module.to_tbc()); let at = b.len() - FOOTER; for field in [8, 12, 16, 20, 24, 32, 40] { let mut bad = b.clone(); bad[at + field] ^= 0xff; assert!(embedded(&bad, t).is_err(), "{field}"); } for cut in 1..=FOOTER + 1 { assert!(embedded(&b[..b.len() - cut], t).is_err()); } let mut bad = b.clone(); bad[300] ^= 1; assert!(embedded(&bad, t).is_err()); } } #[test] fn publication_preserves_originals_on_conflict_failure_race_and_cancel() { let d = Dir::new(); let out = d.0.join("result"); let source = d.0.join("source.bas"); fs::write(&source, b"source").unwrap(); fs::write(&out, b"old").unwrap(); assert!(publish(&out, false, &[], &|| false, |_| panic!( "darf nicht schreiben" )) .is_err()); assert!(publish( &source, true, std::slice::from_ref(&source), &|| false, |_| panic!("Projektschutz") ) .is_err()); assert!(publish(&out, true, &[], &|| false, |p| { fs::write(p, b"new")?; bail!("Finalisierungsfehler") }) .is_err()); assert_eq!(fs::read(&out).unwrap(), b"old"); let cancel = Cell::new(false); assert!(publish(&out, true, &[], &|| cancel.get(), |p| { fs::write(p, b"new")?; cancel.set(true); Ok(()) }) .is_err()); assert_eq!(fs::read(&out).unwrap(), b"old"); assert!(publish(&out, true, &[], &|| true, |_| panic!("abgebrochen")).is_err()); assert!(publish(&out, true, &[], &|| false, |p| { fs::write(p, b"new")?; fs::write(&out, b"external")?; Ok(()) }) .is_err()); assert_eq!(fs::read(&out).unwrap(), b"external"); fs::remove_file(&out).unwrap(); assert!(publish(&out, true, &[], &|| false, |p| { fs::write(p, b"new")?; fs::write(&out, b"racer")?; Ok(()) }) .is_err()); assert_eq!(fs::read(&out).unwrap(), b"racer"); assert!( publish(&d.0.join("missing/result"), false, &[], &|| false, |_| Ok( () )) .is_err() ); publish(&out, true, &[], &|| false, |p| { fs::write(p, b"approved")?; Ok(()) }) .unwrap(); assert_eq!(fs::read(&out).unwrap(), b"approved"); assert_eq!(fs::read(&source).unwrap(), b"source"); assert_eq!(fs::read_dir(&d.0).unwrap().count(), 2); #[cfg(unix)] { std::os::unix::fs::symlink(&source, d.0.join("alias")).unwrap(); assert!( publish(&d.0.join("alias"), true, &[], &|| false, |_| panic!( "Symlink" )) .is_err() ); } } #[test] fn fehlgeschlagene_native_finalisierung_erhaelt_das_ziel() { let dir = Dir::new(); let runtime = dir.0.join("tbrt"); let out = dir.0.join("program"); fs::write(&runtime, header(Target::MacosArm64)).unwrap(); prepare_template(&runtime, Target::MacosArm64).unwrap(); fs::write(&out, b"original").unwrap(); let module = tb_vm::compile_source("TEST", "END\n").unwrap(); let error = export( Export { module: &module, target: Target::MacosArm64, template: &runtime, output: &out, overwrite: true, protected: &[], }, &|| false, ) .unwrap_err(); assert!(error.to_string().contains("Finalisierung"), "{error:#}"); assert_eq!(fs::read(&out).unwrap(), b"original"); assert_eq!(fs::read_dir(&dir.0).unwrap().count(), 3); }