Skip to main content

ghidra_cli/ipc/
client.rs

1//! Bridge client for direct communication with the Java bridge.
2//!
3//! Connects directly to the Java GhidraCliBridge via TCP.
4//! No intermediate daemon process is needed.
5
6use std::io::{BufRead, BufReader, Write};
7use std::net::TcpStream;
8use std::time::Duration;
9
10use anyhow::Result;
11use serde_json::json;
12use tracing::debug;
13
14use super::protocol::{BridgeRequest, BridgeResponse};
15
16/// Client for communicating with the Ghidra Java bridge.
17pub struct BridgeClient {
18    port: u16,
19}
20
21impl BridgeClient {
22    /// Create a client for a known port.
23    pub fn new(port: u16) -> Self {
24        Self { port }
25    }
26
27    /// Get the port this client connects to.
28    #[allow(dead_code)]
29    pub fn port(&self) -> u16 {
30        self.port
31    }
32
33    /// Send a command to the bridge and return the result.
34    pub fn send_command(
35        &self,
36        command: &str,
37        args: Option<serde_json::Value>,
38    ) -> Result<serde_json::Value> {
39        let addr: std::net::SocketAddr = format!("127.0.0.1:{}", self.port)
40            .parse()
41            .map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?;
42        let mut stream =
43            TcpStream::connect_timeout(&addr, Duration::from_secs(10)).map_err(|e| {
44                anyhow::anyhow!("Failed to connect to bridge on port {}: {}", self.port, e)
45            })?;
46        stream.set_read_timeout(Some(Duration::from_secs(300))).ok();
47        stream.set_write_timeout(Some(Duration::from_secs(30))).ok();
48
49        let request = BridgeRequest {
50            command: command.to_string(),
51            args,
52        };
53
54        let request_json = serde_json::to_string(&request)?;
55        debug!("Sending: {}", request_json);
56
57        writeln!(stream, "{}", request_json)?;
58        stream.flush()?;
59
60        let mut reader = BufReader::new(&stream);
61        let mut response_line = String::new();
62        reader.read_line(&mut response_line)?;
63
64        debug!("Received: {}", response_line.trim());
65
66        let response: BridgeResponse = serde_json::from_str(&response_line)?;
67
68        match response.status.as_str() {
69            "success" => Ok(response.data.unwrap_or(json!({}))),
70            "error" => {
71                let msg = response
72                    .message
73                    .unwrap_or_else(|| "Unknown error".to_string());
74                anyhow::bail!("{}", msg)
75            }
76            "shutdown" => Ok(json!({"status": "shutdown"})),
77            _ => Ok(response.data.unwrap_or(json!({}))),
78        }
79    }
80
81    /// Check if bridge is responding.
82    pub fn ping(&self) -> Result<bool> {
83        match self.send_command("ping", None) {
84            Ok(_) => Ok(true),
85            Err(_) => Ok(false),
86        }
87    }
88
89    /// Shutdown the bridge.
90    pub fn shutdown(&self) -> Result<()> {
91        self.send_command("shutdown", None)?;
92        Ok(())
93    }
94
95    /// Get bridge status.
96    #[allow(dead_code)]
97    pub fn status(&self) -> Result<serde_json::Value> {
98        self.send_command("status", None)
99    }
100
101    /// Get bridge info (current program, project name, program count, uptime).
102    pub fn bridge_info(&self) -> Result<serde_json::Value> {
103        self.send_command("bridge_info", None)
104    }
105
106    /// List functions.
107    pub fn list_functions(
108        &self,
109        limit: Option<usize>,
110        filter: Option<String>,
111    ) -> Result<serde_json::Value> {
112        self.send_command(
113            "list_functions",
114            Some(json!({"limit": limit, "filter": filter})),
115        )
116    }
117
118    /// Decompile a function.
119    pub fn decompile(&self, address: String) -> Result<serde_json::Value> {
120        self.send_command("decompile", Some(json!({"address": address})))
121    }
122
123    /// List strings.
124    pub fn list_strings(&self, limit: Option<usize>, filter: Option<String>) -> Result<serde_json::Value> {
125        self.send_command("list_strings", Some(json!({"limit": limit, "filter": filter})))
126    }
127
128    /// List imports.
129    pub fn list_imports(&self) -> Result<serde_json::Value> {
130        self.send_command("list_imports", None)
131    }
132
133    /// List exports.
134    pub fn list_exports(&self) -> Result<serde_json::Value> {
135        self.send_command("list_exports", None)
136    }
137
138    /// Get memory map.
139    pub fn memory_map(&self) -> Result<serde_json::Value> {
140        self.send_command("memory_map", None)
141    }
142
143    /// Get program info.
144    pub fn program_info(&self) -> Result<serde_json::Value> {
145        self.send_command("program_info", None)
146    }
147
148    /// Get cross-references to an address.
149    pub fn xrefs_to(&self, address: String) -> Result<serde_json::Value> {
150        self.send_command("xrefs_to", Some(json!({"address": address})))
151    }
152
153    /// Get cross-references from an address.
154    pub fn xrefs_from(&self, address: String) -> Result<serde_json::Value> {
155        self.send_command("xrefs_from", Some(json!({"address": address})))
156    }
157
158    /// Import a binary.
159    pub fn import_binary(
160        &self,
161        binary_path: &str,
162        program: Option<&str>,
163    ) -> Result<serde_json::Value> {
164        self.send_command(
165            "import",
166            Some(json!({"binary_path": binary_path, "program": program})),
167        )
168    }
169
170    /// Analyze the current program.
171    pub fn analyze(&self) -> Result<serde_json::Value> {
172        self.send_command("analyze", None)
173    }
174
175    /// List programs in the project.
176    pub fn list_programs(&self) -> Result<serde_json::Value> {
177        self.send_command("list_programs", None)
178    }
179
180    /// Open/switch to a program.
181    pub fn open_program(&self, program: &str) -> Result<serde_json::Value> {
182        self.send_command("open_program", Some(json!({"program": program})))
183    }
184
185    // === Extended commands (symbols, types, comments, etc.) ===
186
187    pub fn symbol_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
188        self.send_command("symbol_list", Some(json!({"limit": limit, "filter": filter})))
189    }
190
191    pub fn symbol_get(&self, name: &str) -> Result<serde_json::Value> {
192        self.send_command("symbol_get", Some(json!({"name": name})))
193    }
194
195    pub fn symbol_create(&self, address: &str, name: &str) -> Result<serde_json::Value> {
196        self.send_command(
197            "symbol_create",
198            Some(json!({"address": address, "name": name})),
199        )
200    }
201
202    pub fn symbol_delete(&self, name: &str) -> Result<serde_json::Value> {
203        self.send_command("symbol_delete", Some(json!({"name": name})))
204    }
205
206    pub fn symbol_rename(&self, old_name: &str, new_name: &str) -> Result<serde_json::Value> {
207        self.send_command(
208            "symbol_rename",
209            Some(json!({"old_name": old_name, "new_name": new_name})),
210        )
211    }
212
213    pub fn type_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
214        self.send_command("type_list", Some(json!({"limit": limit, "filter": filter})))
215    }
216
217    pub fn type_get(&self, name: &str) -> Result<serde_json::Value> {
218        self.send_command("type_get", Some(json!({"name": name})))
219    }
220
221    pub fn type_create(&self, definition: &str) -> Result<serde_json::Value> {
222        self.send_command("type_create", Some(json!({"definition": definition})))
223    }
224
225    pub fn type_apply(&self, address: &str, type_name: &str) -> Result<serde_json::Value> {
226        self.send_command(
227            "type_apply",
228            Some(json!({"address": address, "type_name": type_name})),
229        )
230    }
231
232    pub fn comment_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
233        self.send_command("comment_list", Some(json!({"limit": limit, "filter": filter})))
234    }
235
236    pub fn comment_get(&self, address: &str) -> Result<serde_json::Value> {
237        self.send_command("comment_get", Some(json!({"address": address})))
238    }
239
240    pub fn comment_set(
241        &self,
242        address: &str,
243        text: &str,
244        comment_type: Option<&str>,
245    ) -> Result<serde_json::Value> {
246        self.send_command(
247            "comment_set",
248            Some(json!({
249                "address": address,
250                "text": text,
251                "type": comment_type,
252            })),
253        )
254    }
255
256    pub fn comment_delete(&self, address: &str) -> Result<serde_json::Value> {
257        self.send_command("comment_delete", Some(json!({"address": address})))
258    }
259
260    pub fn graph_calls(&self, limit: Option<usize>) -> Result<serde_json::Value> {
261        self.send_command("graph_calls", Some(json!({"limit": limit})))
262    }
263
264    pub fn graph_callers(&self, function: &str, depth: Option<usize>) -> Result<serde_json::Value> {
265        self.send_command(
266            "graph_callers",
267            Some(json!({"function": function, "depth": depth})),
268        )
269    }
270
271    pub fn graph_callees(&self, function: &str, depth: Option<usize>) -> Result<serde_json::Value> {
272        self.send_command(
273            "graph_callees",
274            Some(json!({"function": function, "depth": depth})),
275        )
276    }
277
278    pub fn graph_export(&self, format: &str) -> Result<serde_json::Value> {
279        self.send_command("graph_export", Some(json!({"format": format})))
280    }
281
282    pub fn find_string(&self, pattern: &str) -> Result<serde_json::Value> {
283        self.send_command("find_string", Some(json!({"pattern": pattern})))
284    }
285
286    pub fn find_bytes(&self, hex: &str) -> Result<serde_json::Value> {
287        self.send_command("find_bytes", Some(json!({"hex": hex})))
288    }
289
290    pub fn find_function(&self, pattern: &str) -> Result<serde_json::Value> {
291        self.send_command("find_function", Some(json!({"pattern": pattern})))
292    }
293
294    pub fn find_calls(&self, function: &str) -> Result<serde_json::Value> {
295        self.send_command("find_calls", Some(json!({"function": function})))
296    }
297
298    pub fn find_crypto(&self) -> Result<serde_json::Value> {
299        self.send_command("find_crypto", None)
300    }
301
302    pub fn find_interesting(&self) -> Result<serde_json::Value> {
303        self.send_command("find_interesting", None)
304    }
305
306    pub fn diff_programs(&self, program1: &str, program2: &str) -> Result<serde_json::Value> {
307        self.send_command(
308            "diff_programs",
309            Some(json!({"program1": program1, "program2": program2})),
310        )
311    }
312
313    pub fn diff_functions(&self, func1: &str, func2: &str) -> Result<serde_json::Value> {
314        self.send_command(
315            "diff_functions",
316            Some(json!({"func1": func1, "func2": func2})),
317        )
318    }
319
320    pub fn patch_bytes(&self, address: &str, hex: &str) -> Result<serde_json::Value> {
321        self.send_command("patch_bytes", Some(json!({"address": address, "hex": hex})))
322    }
323
324    pub fn patch_nop(&self, address: &str) -> Result<serde_json::Value> {
325        self.send_command("patch_nop", Some(json!({"address": address})))
326    }
327
328    pub fn patch_export(&self, output: &str) -> Result<serde_json::Value> {
329        self.send_command("patch_export", Some(json!({"output": output})))
330    }
331
332    pub fn disasm(
333        &self,
334        address: &str,
335        num_instructions: Option<usize>,
336    ) -> Result<serde_json::Value> {
337        self.send_command(
338            "disasm",
339            Some(json!({"address": address, "count": num_instructions})),
340        )
341    }
342
343    pub fn stats(&self) -> Result<serde_json::Value> {
344        self.send_command("stats", None)
345    }
346
347    pub fn script_run(&self, script_path: &str, args: &[String]) -> Result<serde_json::Value> {
348        self.send_command(
349            "script_run",
350            Some(json!({"path": script_path, "args": args})),
351        )
352    }
353
354    pub fn script_python(&self, code: &str) -> Result<serde_json::Value> {
355        self.send_command("script_python", Some(json!({"code": code})))
356    }
357
358    pub fn script_java(&self, code: &str) -> Result<serde_json::Value> {
359        self.send_command("script_java", Some(json!({"code": code})))
360    }
361
362    pub fn script_list(&self) -> Result<serde_json::Value> {
363        self.send_command("script_list", None)
364    }
365
366    pub fn program_close(&self) -> Result<serde_json::Value> {
367        self.send_command("close_program", None)
368    }
369
370    pub fn program_delete(&self, program: &str) -> Result<serde_json::Value> {
371        self.send_command("delete_program", Some(json!({"program": program})))
372    }
373
374    pub fn program_export(&self, format: &str, output: Option<&str>) -> Result<serde_json::Value> {
375        self.send_command(
376            "export_program",
377            Some(json!({"format": format, "output": output})),
378        )
379    }
380}