master
c 311 lines 10.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "websocket-jsonrpc.h"
4
5 static int websocket_client_send_json(struct websocket_server_client *wsc, struct json_object *json) {
6 if (!wsc || !json)
7 return -1;
8
9 websocket_debug(wsc, "Sending JSON message");
10
11 // Convert JSON to string
12 const char *json_str = json_object_to_json_string_ext(json, JSON_C_TO_STRING_PLAIN);
13 if (!json_str) {
14 websocket_error(wsc, "Failed to convert JSON to string");
15 return -1;
16 }
17
18 // Send as text message
19 int result = websocket_protocol_send_text(wsc, json_str);
20
21 websocket_debug(wsc, "Sent JSON message, result=%d", result);
22 return result;
23 }
24
25 // Called when a client is connected and ready to exchange messages
26 void jsonrpc_on_connect(struct websocket_server_client *wsc) {
27 if (!wsc) return;
28
29 websocket_debug(wsc, "JSON-RPC client connected");
30
31 // Future implementation - can send a welcome message or initialize client state
32 }
33
34 // Called when a client is about to be disconnected
35 void jsonrpc_on_disconnect(struct websocket_server_client *wsc) {
36 if (!wsc) return;
37
38 websocket_debug(wsc, "JSON-RPC client disconnected");
39
40 // Future implementation - can clean up any client-specific resources
41 }
42
43 // Called before sending a close frame to the client
44 void jsonrpc_on_close(struct websocket_server_client *wsc, WEBSOCKET_CLOSE_CODE code, const char *reason) {
45 if (!wsc) return;
46
47 websocket_debug(wsc, "JSON-RPC client closing with code %d (%s): %s",
48 code,
49 code == WS_CLOSE_NORMAL ? "Normal" :
50 code == WS_CLOSE_GOING_AWAY ? "Going Away" :
51 code == WS_CLOSE_PROTOCOL_ERROR ? "Protocol Error" :
52 code == WS_CLOSE_INTERNAL_ERROR ? "Internal Error" : "Other",
53 reason ? reason : "No reason provided");
54
55 // Future implementation - can send a final message before closing
56 }
57
58 // Adapter function for the on_message callback to match WS_CLIENT callback signature
59 void jsonrpc_on_message_callback(struct websocket_server_client *wsc, const char *message, size_t length, WEBSOCKET_OPCODE opcode) {
60 if (!wsc || !message || length == 0)
61 return;
62
63 // JSON-RPC only works with text messages
64 if (opcode != WS_OPCODE_TEXT) {
65 websocket_error(wsc, "JSON-RPC protocol received non-text message, ignoring");
66 return;
67 }
68
69 websocket_debug(wsc, "JSON-RPC callback processing message: length=%zu", length);
70
71 // Process the message
72 websocket_jsonrpc_process_message(wsc, message, length);
73 }
74
75 // Utility function to extract parameters from a request
76 struct json_object *websocket_jsonrpc_get_params(struct json_object *request) {
77 if (!request)
78 return NULL;
79
80 struct json_object *params = NULL;
81 if (json_object_object_get_ex(request, "params", &params)) {
82 return params; // Return the params object if it exists
83 }
84
85 return NULL; // No params found
86 }
87
88 // Handler for the "echo" method - simply returns the params as the result
89 static void jsonrpc_echo_handler(WS_CLIENT *wsc, struct json_object *request, uint64_t id) {
90 // Get the params if available
91 struct json_object *params = websocket_jsonrpc_get_params(request);
92
93 // Clone the params to avoid ownership issues
94 struct json_object *result = params ? json_object_get(params) : json_object_new_object();
95
96 // Send response
97 websocket_jsonrpc_response_result(wsc, result, id);
98 }
99
100 // Define a fixed array of method handlers
101 static struct {
102 const char *method;
103 jsonrpc_method_handler handler;
104 } jsonrpc_methods[] = {
105 { "echo", jsonrpc_echo_handler },
106
107 // Add more methods here as needed
108 // { "method_name", method_handler_function },
109
110 // Terminator
111 { NULL, NULL }
112 };
113
114 // Initialize the JSON-RPC protocol
115 void websocket_jsonrpc_initialize(void) {
116 netdata_log_info("JSON-RPC protocol initialized with built-in methods");
117 }
118
119 // Find a method handler
120 static jsonrpc_method_handler find_method_handler(const char *method) {
121 if (!method)
122 return NULL;
123
124 // Simple linear search through the fixed array
125 for (int i = 0; jsonrpc_methods[i].method != NULL; i++) {
126 if (strcmp(jsonrpc_methods[i].method, method) == 0) {
127 return jsonrpc_methods[i].handler;
128 }
129 }
130
131 return NULL;
132 }
133
134 // Validate JSON-RPC request according to specification
135 bool websocket_jsonrpc_validate_request(struct json_object *request) {
136 if (!request || json_object_get_type(request) != json_type_object)
137 return false;
138
139 // Check for required fields
140 struct json_object *jsonrpc, *method;
141
142 if (!json_object_object_get_ex(request, "jsonrpc", &jsonrpc) ||
143 !json_object_object_get_ex(request, "method", &method))
144 return false;
145
146 // Validate jsonrpc version
147 if (json_object_get_type(jsonrpc) != json_type_string ||
148 strcmp(json_object_get_string(jsonrpc), JSONRPC_VERSION) != 0)
149 return false;
150
151 // Validate method
152 if (json_object_get_type(method) != json_type_string)
153 return false;
154
155 return true;
156 }
157
158 // Process a JSON-RPC request
159 static void process_jsonrpc_request(WS_CLIENT *wsc, struct json_object *request) {
160 if (!websocket_jsonrpc_validate_request(request)) {
161 websocket_jsonrpc_response_error(wsc, JSONRPC_ERROR_INVALID_REQUEST,
162 "Invalid JSON-RPC request", 0);
163 return;
164 }
165
166 // Extract request components
167 struct json_object *method_obj, *id_obj = NULL;
168
169 json_object_object_get_ex(request, "method", &method_obj);
170 const char *method = json_object_get_string(method_obj);
171
172 // Get ID if present (0 indicates a notification that requires no response)
173 uint64_t id = 0;
174 bool has_id = json_object_object_get_ex(request, "id", &id_obj);
175 if (has_id && id_obj && json_object_get_type(id_obj) != json_type_null) {
176 if (json_object_get_type(id_obj) == json_type_int)
177 id = json_object_get_int64(id_obj);
178 else if (json_object_get_type(id_obj) == json_type_string) {
179 // Try to convert string ID to integer if possible
180 const char *id_str = json_object_get_string(id_obj);
181 char *endptr;
182 id = (uint64_t)strtoll(id_str, &endptr, 10);
183 if (*endptr != '\0') {
184 // Not a number, just hash the string for an ID
185 id = simple_hash(id_str);
186 }
187 }
188 }
189
190 // Find handler for the requested method
191 jsonrpc_method_handler handler = find_method_handler(method);
192 if (!handler) {
193 if (has_id) {
194 websocket_jsonrpc_response_error(wsc, JSONRPC_ERROR_METHOD_NOT_FOUND,
195 "Method not found", id);
196 }
197 return;
198 }
199
200 // Call the handler with the request
201 handler(wsc, request, id);
202 }
203
204 // Process a WebSocket message as JSON-RPC
205 bool websocket_jsonrpc_process_message(WS_CLIENT *wsc, const char *message, size_t length) {
206 if (!wsc || !message || length == 0)
207 return false;
208
209 websocket_debug(wsc, "Processing JSON-RPC message: length=%zu", length);
210
211 // Parse the JSON
212 struct json_object *json = json_tokener_parse(message);
213 if (!json) {
214 websocket_error(wsc, "Failed to parse JSON-RPC message");
215 websocket_jsonrpc_response_error(wsc, JSONRPC_ERROR_PARSE_ERROR,
216 "Parse error", 0);
217 return false;
218 }
219
220 // Process based on message type
221 if (json_object_get_type(json) == json_type_array) {
222 // Batch request
223 websocket_debug(wsc, "Processing JSON-RPC batch request");
224
225 int array_len = json_object_array_length(json);
226 for (int i = 0; i < array_len; i++) {
227 struct json_object *request = json_object_array_get_idx(json, i);
228 process_jsonrpc_request(wsc, request);
229 }
230 }
231 else if (json_object_get_type(json) == json_type_object) {
232 // Single request
233 process_jsonrpc_request(wsc, json);
234 }
235 else {
236 // Invalid request
237 websocket_jsonrpc_response_error(wsc, JSONRPC_ERROR_INVALID_REQUEST,
238 "Invalid request", 0);
239 json_object_put(json);
240 return false;
241 }
242
243 json_object_put(json);
244 return true;
245 }
246
247 // Create and send a JSON-RPC success response
248 void websocket_jsonrpc_response_result(WS_CLIENT *wsc, struct json_object *result, uint64_t id) {
249 if (!wsc || id == 0) // No response for notifications (id == 0)
250 return;
251
252 struct json_object *response = json_object_new_object();
253
254 // Add required fields
255 json_object_object_add(response, "jsonrpc", json_object_new_string(JSONRPC_VERSION));
256
257 // Add result (takes ownership of the result object)
258 if (result) {
259 json_object_object_add(response, "result", result);
260 } else {
261 json_object_object_add(response, "result", json_object_new_object());
262 }
263
264 // Add ID
265 json_object_object_add(response, "id", json_object_new_int64(id));
266
267 // Send the response
268 websocket_client_send_json(wsc, response);
269
270 // Free the response object
271 json_object_put(response);
272 }
273
274 // Create and send a JSON-RPC error response
275 void websocket_jsonrpc_response_error(WS_CLIENT *wsc, JSONRPC_ERROR_CODE code, const char *message, uint64_t id) {
276 websocket_jsonrpc_response_error_with_data(wsc, code, message, NULL, id);
277 }
278
279 // Create and send a JSON-RPC error response with additional data
280 void websocket_jsonrpc_response_error_with_data(WS_CLIENT *wsc, JSONRPC_ERROR_CODE code, const char *message,
281 struct json_object *data, uint64_t id) {
282 if (!wsc || id == 0) // No response for notifications (id == 0)
283 return;
284
285 struct json_object *response = json_object_new_object();
286 struct json_object *error = json_object_new_object();
287
288 // Add required fields
289 json_object_object_add(response, "jsonrpc", json_object_new_string(JSONRPC_VERSION));
290
291 // Add error code and message
292 json_object_object_add(error, "code", json_object_new_int(code));
293 json_object_object_add(error, "message", json_object_new_string(message ? message : "Unknown error"));
294
295 // Add error data if provided
296 if (data) {
297 json_object_object_add(error, "data", data);
298 }
299
300 // Add error object to response
301 json_object_object_add(response, "error", error);
302
303 // Add ID
304 json_object_object_add(response, "id", json_object_new_int64(id));
305
306 // Send the response
307 websocket_client_send_json(wsc, response);
308
309 // Free the response object
310 json_object_put(response);
311 }