Skip to main content

ghidra_cli/ipc/
protocol.rs

1//! IPC protocol message types.
2//!
3//! Defines the request/response format for CLI ↔ daemon communication.
4//! Uses a typed command enum (not wrapping CLI Commands) for clean separation.
5
6#![allow(dead_code)]
7
8use serde::{Deserialize, Serialize};
9
10/// IPC request from CLI to daemon.
11#[derive(Debug, Serialize, Deserialize)]
12pub struct Request {
13    /// Request ID for matching responses
14    pub id: u64,
15    /// The command to execute
16    pub command: Command,
17}
18
19impl Request {
20    /// Create a new request with the given ID and command.
21    pub fn new(id: u64, command: Command) -> Self {
22        Self { id, command }
23    }
24}
25
26/// IPC response from daemon to CLI.
27#[derive(Debug, Serialize, Deserialize)]
28pub struct Response {
29    /// Request ID this response corresponds to
30    pub id: u64,
31    /// Whether the command succeeded
32    pub success: bool,
33    /// Result data on success
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub result: Option<serde_json::Value>,
36    /// Error message on failure
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub error: Option<String>,
39}
40
41impl Response {
42    /// Create a success response.
43    pub fn success(id: u64, result: serde_json::Value) -> Self {
44        Self {
45            id,
46            success: true,
47            result: Some(result),
48            error: None,
49        }
50    }
51
52    /// Create an error response.
53    pub fn error(id: u64, message: impl Into<String>) -> Self {
54        Self {
55            id,
56            success: false,
57            result: None,
58            error: Some(message.into()),
59        }
60    }
61
62    /// Create a success response with no data.
63    pub fn ok(id: u64) -> Self {
64        Self {
65            id,
66            success: true,
67            result: Some(serde_json::json!({})),
68            error: None,
69        }
70    }
71}
72
73/// Commands that can be sent from CLI to daemon.
74///
75/// These are separate from CLI Commands to decouple IPC from argument parsing.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(tag = "type", rename_all = "snake_case")]
78pub enum Command {
79    // === Data Queries ===
80    /// List functions in the program
81    ListFunctions {
82        #[serde(skip_serializing_if = "Option::is_none")]
83        limit: Option<usize>,
84        #[serde(skip_serializing_if = "Option::is_none")]
85        filter: Option<String>,
86    },
87
88    /// Decompile a function at address
89    Decompile { address: String },
90
91    /// List strings in the program
92    ListStrings {
93        #[serde(skip_serializing_if = "Option::is_none")]
94        limit: Option<usize>,
95    },
96
97    /// List imports
98    ListImports,
99
100    /// List exports
101    ListExports,
102
103    /// Get memory map
104    MemoryMap,
105
106    /// Get program info
107    ProgramInfo,
108
109    /// Get cross-references to an address
110    XRefsTo { address: String },
111
112    /// Get cross-references from an address
113    XRefsFrom { address: String },
114
115    // === Project Management ===
116    /// Import a binary into a project
117    Import {
118        binary_path: String,
119        project: String,
120        #[serde(skip_serializing_if = "Option::is_none")]
121        program: Option<String>,
122    },
123
124    /// Analyze a program in a project
125    Analyze { project: String, program: String },
126
127    // === Session Management ===
128    /// Health check
129    Ping,
130
131    /// Get daemon status
132    Status,
133
134    /// Clear result cache
135    ClearCache,
136
137    /// Shutdown the daemon
138    Shutdown,
139
140    // === Generic CLI Command Forwarding ===
141    /// Execute a CLI command through the daemon's queue
142    ExecuteCli {
143        /// The serialized CLI command
144        command_json: String,
145    },
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_request_serialization() {
154        let request = Request::new(1, Command::Ping);
155        let json = serde_json::to_string(&request).unwrap();
156        let deserialized: Request = serde_json::from_str(&json).unwrap();
157        assert_eq!(deserialized.id, 1);
158        assert!(matches!(deserialized.command, Command::Ping));
159    }
160
161    #[test]
162    fn test_response_success() {
163        let response = Response::success(1, serde_json::json!({"count": 42}));
164        assert!(response.success);
165        assert_eq!(response.id, 1);
166        assert!(response.result.is_some());
167    }
168
169    #[test]
170    fn test_response_error() {
171        let response = Response::error(1, "Something went wrong");
172        assert!(!response.success);
173        assert_eq!(response.error.as_ref().unwrap(), "Something went wrong");
174    }
175
176    #[test]
177    fn test_command_serialization() {
178        let cmd = Command::ListFunctions {
179            limit: Some(100),
180            filter: Some("main".to_string()),
181        };
182        let json = serde_json::to_string(&cmd).unwrap();
183        assert!(json.contains("list_functions"));
184        assert!(json.contains("100"));
185        assert!(json.contains("main"));
186    }
187}