blockless_sdk/
rpc.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4// FFI bindings for the new unified RPC interface
5#[cfg(not(feature = "mock-ffi"))]
6#[link(wasm_import_module = "bless")]
7extern "C" {
8    #[link_name = "rpc_call"]
9    fn rpc_call(
10        request_ptr: *const u8,
11        request_len: u32,
12        response_ptr: *mut u8,
13        response_max_len: u32,
14        bytes_written: *mut u32,
15    ) -> u32;
16}
17
18#[cfg(feature = "mock-ffi")]
19#[allow(unused_variables)]
20mod mock_ffi {
21    pub unsafe fn rpc_call(
22        _request_ptr: *const u8,
23        _request_len: u32,
24        _response_ptr: *mut u8,
25        _response_max_len: u32,
26        _bytes_written: *mut u32,
27    ) -> u32 {
28        // Mock implementation for testing
29        0
30    }
31}
32
33#[cfg(feature = "mock-ffi")]
34use mock_ffi::*;
35
36#[derive(Debug, Clone)]
37pub enum RpcError {
38    InvalidJson,
39    MethodNotFound,
40    InvalidParams,
41    InternalError,
42    BufferTooSmall,
43    Utf8Error,
44}
45
46impl std::fmt::Display for RpcError {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            RpcError::InvalidJson => write!(f, "Invalid JSON format"),
50            RpcError::MethodNotFound => write!(f, "Method not found"),
51            RpcError::InvalidParams => write!(f, "Invalid parameters"),
52            RpcError::InternalError => write!(f, "Internal error"),
53            RpcError::BufferTooSmall => write!(f, "Buffer too small"),
54            RpcError::Utf8Error => write!(f, "UTF-8 conversion error"),
55        }
56    }
57}
58
59impl std::error::Error for RpcError {}
60
61// JSON-RPC 2.0 structures
62#[derive(Serialize, Deserialize, Debug)]
63pub struct JsonRpcRequest<T> {
64    pub jsonrpc: String,
65    pub method: String,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub params: Option<T>,
68    pub id: u32,
69}
70
71#[derive(Serialize, Deserialize, Debug)]
72pub struct JsonRpcResponse<T> {
73    pub jsonrpc: String,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub result: Option<T>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub error: Option<JsonRpcError>,
78    pub id: u32,
79}
80
81#[derive(Serialize, Deserialize, Debug)]
82pub struct JsonRpcError {
83    pub code: i32,
84    pub message: String,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub data: Option<serde_json::Value>,
87}
88
89/// Unified RPC client for calling host functions
90pub struct RpcClient {
91    next_id: u32,
92    buffer_size: usize,
93}
94
95impl Default for RpcClient {
96    fn default() -> Self {
97        Self::with_buffer_size(4096) // Default 4KB buffer
98    }
99}
100
101impl RpcClient {
102    pub fn new() -> Self {
103        Self::default()
104    }
105
106    pub fn with_buffer_size(buffer_size: usize) -> Self {
107        Self {
108            next_id: 1,
109            buffer_size,
110        }
111    }
112
113    pub fn set_buffer_size(&mut self, buffer_size: usize) {
114        self.buffer_size = buffer_size;
115    }
116
117    pub fn buffer_size(&self) -> usize {
118        self.buffer_size
119    }
120
121    pub fn call<P: Serialize, R: serde::de::DeserializeOwned>(
122        &mut self,
123        method: &str,
124        params: Option<P>,
125    ) -> Result<JsonRpcResponse<R>, RpcError> {
126        let request = JsonRpcRequest {
127            jsonrpc: "2.0".to_string(),
128            method: method.to_string(),
129            params,
130            id: self.next_id,
131        };
132
133        self.next_id += 1;
134        let request_bytes = serde_json::to_vec(&request).map_err(|_| RpcError::InvalidJson)?;
135        let mut response_buffer = vec![0u8; self.buffer_size];
136        let mut bytes_written = 0u32;
137        let result = unsafe {
138            rpc_call(
139                request_bytes.as_ptr(),
140                request_bytes.len() as u32,
141                response_buffer.as_mut_ptr(),
142                response_buffer.len() as u32,
143                &mut bytes_written as *mut u32,
144            )
145        };
146        if result != 0 {
147            return match result {
148                1 => Err(RpcError::InvalidJson),
149                2 => Err(RpcError::MethodNotFound),
150                3 => Err(RpcError::InvalidParams),
151                4 => Err(RpcError::InternalError),
152                5 => Err(RpcError::BufferTooSmall),
153                _ => Err(RpcError::InternalError),
154            };
155        }
156        response_buffer.truncate(bytes_written as usize);
157        serde_json::from_slice(&response_buffer).map_err(|_| RpcError::InvalidJson)
158    }
159
160    /// Convenience method for ping
161    pub fn ping(&mut self) -> Result<String, RpcError> {
162        let response: JsonRpcResponse<String> = self.call("ping", None::<()>)?;
163        response.result.ok_or(RpcError::InternalError)
164    }
165
166    /// Convenience method for echo
167    pub fn echo<T: Serialize + serde::de::DeserializeOwned>(
168        &mut self,
169        data: T,
170    ) -> Result<T, RpcError> {
171        let response: JsonRpcResponse<T> = self.call("echo", Some(data))?;
172        response.result.ok_or(RpcError::InternalError)
173    }
174
175    /// Convenience method for getting version
176    pub fn version(&mut self) -> Result<HashMap<String, String>, RpcError> {
177        let response: JsonRpcResponse<HashMap<String, String>> =
178            self.call("version", None::<()>)?;
179        response.result.ok_or(RpcError::InternalError)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_rpc_request_serialization() {
189        let request: JsonRpcRequest<()> = JsonRpcRequest {
190            jsonrpc: "2.0".to_string(),
191            method: "ping".to_string(),
192            params: None,
193            id: 1,
194        };
195
196        let json_str = serde_json::to_string(&request).unwrap();
197        assert!(json_str.contains("\"jsonrpc\":\"2.0\""));
198        assert!(json_str.contains("\"method\":\"ping\""));
199        assert!(json_str.contains("\"id\":1"));
200    }
201
202    #[test]
203    fn test_rpc_response_deserialization() {
204        let json_str = r#"{"jsonrpc":"2.0","result":"pong","id":1}"#;
205        let response: JsonRpcResponse<String> = serde_json::from_str(json_str).unwrap();
206
207        assert_eq!(response.jsonrpc, "2.0");
208        assert_eq!(response.result, Some("pong".to_string()));
209        assert_eq!(response.id, 1);
210        assert!(response.error.is_none());
211    }
212}