ghidra_mcp/
worker_asset.rs1use sha2::{Digest, Sha256};
7use std::io;
8use std::path::{Path, PathBuf};
9
10pub const WORKER_SCRIPT_NAME: &str = "GhidraMcpWorker.java";
17
18pub const WORKER_JAVA: &str = include_str!("../worker/GhidraMcpWorker.java");
20
21pub fn extract_worker(script_dir: &Path) -> io::Result<PathBuf> {
24 std::fs::create_dir_all(script_dir)?;
25 let path = script_dir.join(WORKER_SCRIPT_NAME);
26 let want = hash(WORKER_JAVA.as_bytes());
27 let up_to_date = match std::fs::read(&path) {
28 Ok(existing) => hash(&existing) == want,
29 Err(_) => false,
30 };
31 if !up_to_date {
32 std::fs::write(&path, WORKER_JAVA.as_bytes())?;
33 }
34 Ok(path)
35}
36
37fn hash(bytes: &[u8]) -> [u8; 32] {
38 let mut h = Sha256::new();
39 h.update(bytes);
40 h.finalize().into()
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn embedded_source_is_the_worker_script() {
49 assert!(WORKER_JAVA.contains("class GhidraMcpWorker"));
50 assert!(WORKER_JAVA.contains("worker/init"));
51 }
52
53 #[test]
54 fn extract_writes_then_is_idempotent_by_hash() {
55 let dir = std::env::temp_dir().join(format!("ghidra-mcp-asset-{}", uuid::Uuid::new_v4()));
56 let first = extract_worker(&dir).unwrap();
57 assert!(first.exists());
58 assert_eq!(first.file_name().unwrap(), WORKER_SCRIPT_NAME);
59 let before = std::fs::metadata(&first).unwrap().modified().unwrap();
60 std::thread::sleep(std::time::Duration::from_millis(20));
62 let second = extract_worker(&dir).unwrap();
63 let after = std::fs::metadata(&second).unwrap().modified().unwrap();
64 assert_eq!(before, after, "unchanged content must not be rewritten");
65 std::fs::remove_dir_all(&dir).ok();
66 }
67
68 #[test]
72 fn script_filename_matches_the_public_class_it_contains() {
73 let stem = WORKER_SCRIPT_NAME
74 .strip_suffix(".java")
75 .expect("worker script is a .java file");
76 assert!(
77 WORKER_JAVA.contains(&format!("class {stem}")),
78 "{WORKER_SCRIPT_NAME} does not declare `class {stem}`"
79 );
80 }
81}