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(
120        &self,
121        address: String,
122        with_vars: bool,
123        with_params: bool,
124    ) -> Result<serde_json::Value> {
125        self.send_command(
126            "decompile",
127            Some(json!({
128                "address": address,
129                "with_vars": with_vars,
130                "with_params": with_params,
131            })),
132        )
133    }
134
135    /// List strings.
136    pub fn list_strings(&self, limit: Option<usize>, filter: Option<String>) -> Result<serde_json::Value> {
137        self.send_command("list_strings", Some(json!({"limit": limit, "filter": filter})))
138    }
139
140    /// List imports.
141    pub fn list_imports(&self) -> Result<serde_json::Value> {
142        self.send_command("list_imports", None)
143    }
144
145    /// List exports.
146    pub fn list_exports(&self) -> Result<serde_json::Value> {
147        self.send_command("list_exports", None)
148    }
149
150    /// Get memory map.
151    pub fn memory_map(&self) -> Result<serde_json::Value> {
152        self.send_command("memory_map", None)
153    }
154
155    /// Get program info.
156    pub fn program_info(&self) -> Result<serde_json::Value> {
157        self.send_command("program_info", None)
158    }
159
160    /// Get cross-references to an address.
161    pub fn xrefs_to(&self, address: String) -> Result<serde_json::Value> {
162        self.send_command("xrefs_to", Some(json!({"address": address})))
163    }
164
165    /// Get cross-references from an address.
166    pub fn xrefs_from(&self, address: String) -> Result<serde_json::Value> {
167        self.send_command("xrefs_from", Some(json!({"address": address})))
168    }
169
170    /// Import a binary.
171    pub fn import_binary(
172        &self,
173        binary_path: &str,
174        program: Option<&str>,
175    ) -> Result<serde_json::Value> {
176        self.send_command(
177            "import",
178            Some(json!({"binary_path": binary_path, "program": program})),
179        )
180    }
181
182    /// Analyze the current program.
183    pub fn analyze(&self) -> Result<serde_json::Value> {
184        self.send_command("analyze", None)
185    }
186
187    /// List programs in the project.
188    pub fn list_programs(&self) -> Result<serde_json::Value> {
189        self.send_command("list_programs", None)
190    }
191
192    /// Open/switch to a program.
193    pub fn open_program(&self, program: &str) -> Result<serde_json::Value> {
194        self.send_command("open_program", Some(json!({"program": program})))
195    }
196
197    // === Extended commands (symbols, types, comments, etc.) ===
198
199    pub fn symbol_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
200        self.send_command("symbol_list", Some(json!({"limit": limit, "filter": filter})))
201    }
202
203    pub fn symbol_get(&self, name: &str) -> Result<serde_json::Value> {
204        self.send_command("symbol_get", Some(json!({"name": name})))
205    }
206
207    pub fn symbol_create(&self, address: &str, name: &str) -> Result<serde_json::Value> {
208        self.send_command(
209            "symbol_create",
210            Some(json!({"address": address, "name": name})),
211        )
212    }
213
214    pub fn symbol_delete(&self, name: &str) -> Result<serde_json::Value> {
215        self.send_command("symbol_delete", Some(json!({"name": name})))
216    }
217
218    pub fn symbol_rename(&self, old_name: &str, new_name: &str) -> Result<serde_json::Value> {
219        self.send_command(
220            "symbol_rename",
221            Some(json!({"old_name": old_name, "new_name": new_name})),
222        )
223    }
224
225    pub fn type_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
226        self.send_command("type_list", Some(json!({"limit": limit, "filter": filter})))
227    }
228
229    pub fn type_get(&self, name: &str) -> Result<serde_json::Value> {
230        self.send_command("type_get", Some(json!({"name": name})))
231    }
232
233    pub fn type_create(&self, definition: &str) -> Result<serde_json::Value> {
234        self.send_command("type_create", Some(json!({"definition": definition})))
235    }
236
237    pub fn type_apply(&self, address: &str, type_name: &str) -> Result<serde_json::Value> {
238        self.send_command(
239            "type_apply",
240            Some(json!({"address": address, "type_name": type_name})),
241        )
242    }
243
244    pub fn comment_list(&self, limit: Option<usize>, filter: Option<&str>) -> Result<serde_json::Value> {
245        self.send_command("comment_list", Some(json!({"limit": limit, "filter": filter})))
246    }
247
248    pub fn comment_get(&self, address: &str) -> Result<serde_json::Value> {
249        self.send_command("comment_get", Some(json!({"address": address})))
250    }
251
252    pub fn comment_set(
253        &self,
254        address: &str,
255        text: &str,
256        comment_type: Option<&str>,
257    ) -> Result<serde_json::Value> {
258        self.send_command(
259            "comment_set",
260            Some(json!({
261                "address": address,
262                "text": text,
263                "type": comment_type,
264            })),
265        )
266    }
267
268    pub fn comment_delete(&self, address: &str) -> Result<serde_json::Value> {
269        self.send_command("comment_delete", Some(json!({"address": address})))
270    }
271
272    pub fn graph_calls(&self, limit: Option<usize>) -> Result<serde_json::Value> {
273        self.send_command("graph_calls", Some(json!({"limit": limit})))
274    }
275
276    pub fn graph_callers(&self, function: &str, depth: Option<usize>) -> Result<serde_json::Value> {
277        self.send_command(
278            "graph_callers",
279            Some(json!({"function": function, "depth": depth})),
280        )
281    }
282
283    pub fn graph_callees(&self, function: &str, depth: Option<usize>) -> Result<serde_json::Value> {
284        self.send_command(
285            "graph_callees",
286            Some(json!({"function": function, "depth": depth})),
287        )
288    }
289
290    pub fn graph_export(&self, format: &str) -> Result<serde_json::Value> {
291        self.send_command("graph_export", Some(json!({"format": format})))
292    }
293
294    pub fn find_string(&self, pattern: &str) -> Result<serde_json::Value> {
295        self.send_command("find_string", Some(json!({"pattern": pattern})))
296    }
297
298    pub fn find_bytes(&self, hex: &str) -> Result<serde_json::Value> {
299        self.send_command("find_bytes", Some(json!({"hex": hex})))
300    }
301
302    pub fn find_function(&self, pattern: &str) -> Result<serde_json::Value> {
303        self.send_command("find_function", Some(json!({"pattern": pattern})))
304    }
305
306    pub fn find_calls(&self, function: &str) -> Result<serde_json::Value> {
307        self.send_command("find_calls", Some(json!({"function": function})))
308    }
309
310    pub fn find_crypto(&self) -> Result<serde_json::Value> {
311        self.send_command("find_crypto", None)
312    }
313
314    pub fn find_interesting(&self) -> Result<serde_json::Value> {
315        self.send_command("find_interesting", None)
316    }
317
318    pub fn diff_programs(&self, program1: &str, program2: &str) -> Result<serde_json::Value> {
319        self.send_command(
320            "diff_programs",
321            Some(json!({"program1": program1, "program2": program2})),
322        )
323    }
324
325    pub fn diff_functions(&self, func1: &str, func2: &str) -> Result<serde_json::Value> {
326        self.send_command(
327            "diff_functions",
328            Some(json!({"func1": func1, "func2": func2})),
329        )
330    }
331
332    pub fn patch_bytes(&self, address: &str, hex: &str) -> Result<serde_json::Value> {
333        self.send_command("patch_bytes", Some(json!({"address": address, "hex": hex})))
334    }
335
336    pub fn patch_nop(&self, address: &str) -> Result<serde_json::Value> {
337        self.send_command("patch_nop", Some(json!({"address": address})))
338    }
339
340    pub fn patch_export(&self, output: &str) -> Result<serde_json::Value> {
341        self.send_command("patch_export", Some(json!({"output": output})))
342    }
343
344    pub fn disasm(
345        &self,
346        address: &str,
347        num_instructions: Option<usize>,
348    ) -> Result<serde_json::Value> {
349        self.send_command(
350            "disasm",
351            Some(json!({"address": address, "count": num_instructions})),
352        )
353    }
354
355    pub fn stats(&self) -> Result<serde_json::Value> {
356        self.send_command("stats", None)
357    }
358
359    pub fn script_run(&self, script_path: &str, args: &[String]) -> Result<serde_json::Value> {
360        self.send_command(
361            "script_run",
362            Some(json!({"path": script_path, "args": args})),
363        )
364    }
365
366    pub fn script_python(&self, code: &str) -> Result<serde_json::Value> {
367        self.send_command("script_python", Some(json!({"code": code})))
368    }
369
370    pub fn script_java(&self, code: &str) -> Result<serde_json::Value> {
371        self.send_command("script_java", Some(json!({"code": code})))
372    }
373
374    pub fn script_list(&self) -> Result<serde_json::Value> {
375        self.send_command("script_list", None)
376    }
377
378    pub fn program_close(&self) -> Result<serde_json::Value> {
379        self.send_command("close_program", None)
380    }
381
382    pub fn program_delete(&self, program: &str) -> Result<serde_json::Value> {
383        self.send_command("delete_program", Some(json!({"program": program})))
384    }
385
386    pub fn program_export(&self, format: &str, output: Option<&str>) -> Result<serde_json::Value> {
387        self.send_command(
388            "export_program",
389            Some(json!({"format": format, "output": output})),
390        )
391    }
392}