1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <stdbool.h> |
5 | #include <inttypes.h> |
6 | |
7 | #include <msgpack.h> |
8 | |
9 | #include "nvim/api/private/dispatch.h" |
10 | #include "nvim/api/private/helpers.h" |
11 | #include "nvim/msgpack_rpc/helpers.h" |
12 | #include "nvim/lib/kvec.h" |
13 | #include "nvim/vim.h" |
14 | #include "nvim/log.h" |
15 | #include "nvim/memory.h" |
16 | #include "nvim/assert.h" |
17 | |
18 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
19 | # include "msgpack_rpc/helpers.c.generated.h" |
20 | #endif |
21 | |
22 | static msgpack_zone zone; |
23 | static msgpack_sbuffer sbuffer; |
24 | |
25 | #define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ |
26 | static bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \ |
27 | Integer *const arg) \ |
28 | FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ |
29 | { \ |
30 | if (obj->type != MSGPACK_OBJECT_EXT \ |
31 | || obj->via.ext.type + EXT_OBJECT_TYPE_SHIFT != kObjectType##t) { \ |
32 | return false; \ |
33 | } \ |
34 | \ |
35 | msgpack_object data; \ |
36 | msgpack_unpack_return ret = msgpack_unpack(obj->via.ext.ptr, \ |
37 | obj->via.ext.size, \ |
38 | NULL, \ |
39 | &zone, \ |
40 | &data); \ |
41 | \ |
42 | if (ret != MSGPACK_UNPACK_SUCCESS) { \ |
43 | return false; \ |
44 | } \ |
45 | \ |
46 | *arg = (handle_T)data.via.i64; \ |
47 | return true; \ |
48 | } \ |
49 | \ |
50 | static void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \ |
51 | FUNC_ATTR_NONNULL_ARG(2) \ |
52 | { \ |
53 | msgpack_packer pac; \ |
54 | msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \ |
55 | msgpack_pack_int64(&pac, (handle_T)o); \ |
56 | msgpack_pack_ext(res, sbuffer.size, \ |
57 | kObjectType##t - EXT_OBJECT_TYPE_SHIFT); \ |
58 | msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \ |
59 | msgpack_sbuffer_clear(&sbuffer); \ |
60 | } |
61 | |
62 | void msgpack_rpc_helpers_init(void) |
63 | { |
64 | msgpack_zone_init(&zone, 0xfff); |
65 | msgpack_sbuffer_init(&sbuffer); |
66 | } |
67 | |
68 | HANDLE_TYPE_CONVERSION_IMPL(Buffer, buffer) |
69 | HANDLE_TYPE_CONVERSION_IMPL(Window, window) |
70 | HANDLE_TYPE_CONVERSION_IMPL(Tabpage, tabpage) |
71 | |
72 | typedef struct { |
73 | const msgpack_object *mobj; |
74 | Object *aobj; |
75 | bool container; |
76 | size_t idx; |
77 | } MPToAPIObjectStackItem; |
78 | |
79 | /// Convert type used by msgpack parser to Nvim API type. |
80 | /// |
81 | /// @param[in] obj Msgpack value to convert. |
82 | /// @param[out] arg Location where result of conversion will be saved. |
83 | /// |
84 | /// @return true in case of success, false otherwise. |
85 | bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) |
86 | FUNC_ATTR_NONNULL_ALL |
87 | { |
88 | bool ret = true; |
89 | kvec_t(MPToAPIObjectStackItem) stack = KV_INITIAL_VALUE; |
90 | kv_push(stack, ((MPToAPIObjectStackItem) { |
91 | .mobj = obj, |
92 | .aobj = arg, |
93 | .container = false, |
94 | .idx = 0, |
95 | })); |
96 | while (ret && kv_size(stack)) { |
97 | MPToAPIObjectStackItem cur = kv_last(stack); |
98 | if (!cur.container) { |
99 | *cur.aobj = NIL; |
100 | } |
101 | switch (cur.mobj->type) { |
102 | case MSGPACK_OBJECT_NIL: { |
103 | break; |
104 | } |
105 | case MSGPACK_OBJECT_BOOLEAN: { |
106 | *cur.aobj = BOOLEAN_OBJ(cur.mobj->via.boolean); |
107 | break; |
108 | } |
109 | case MSGPACK_OBJECT_NEGATIVE_INTEGER: { |
110 | STATIC_ASSERT(sizeof(Integer) == sizeof(cur.mobj->via.i64), |
111 | "Msgpack integer size does not match API integer" ); |
112 | *cur.aobj = INTEGER_OBJ(cur.mobj->via.i64); |
113 | break; |
114 | } |
115 | case MSGPACK_OBJECT_POSITIVE_INTEGER: { |
116 | STATIC_ASSERT(sizeof(Integer) == sizeof(cur.mobj->via.u64), |
117 | "Msgpack integer size does not match API integer" ); |
118 | if (cur.mobj->via.u64 > API_INTEGER_MAX) { |
119 | ret = false; |
120 | } else { |
121 | *cur.aobj = INTEGER_OBJ((Integer)cur.mobj->via.u64); |
122 | } |
123 | break; |
124 | } |
125 | #ifdef NVIM_MSGPACK_HAS_FLOAT32 |
126 | case MSGPACK_OBJECT_FLOAT32: |
127 | case MSGPACK_OBJECT_FLOAT64: |
128 | #else |
129 | case MSGPACK_OBJECT_FLOAT: |
130 | #endif |
131 | { |
132 | STATIC_ASSERT(sizeof(Float) == sizeof(cur.mobj->via.f64), |
133 | "Msgpack floating-point size does not match API integer" ); |
134 | *cur.aobj = FLOAT_OBJ(cur.mobj->via.f64); |
135 | break; |
136 | } |
137 | #define STR_CASE(type, attr, obj, dest, conv) \ |
138 | case type: { \ |
139 | dest = conv(((String) { \ |
140 | .size = obj->via.attr.size, \ |
141 | .data = (obj->via.attr.ptr == NULL || obj->via.attr.size == 0 \ |
142 | ? xmemdupz("", 0) \ |
143 | : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), \ |
144 | })); \ |
145 | break; \ |
146 | } |
147 | STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ) |
148 | STR_CASE(MSGPACK_OBJECT_BIN, bin, cur.mobj, *cur.aobj, STRING_OBJ) |
149 | case MSGPACK_OBJECT_ARRAY: { |
150 | const size_t size = cur.mobj->via.array.size; |
151 | if (cur.container) { |
152 | if (cur.idx >= size) { |
153 | (void)kv_pop(stack); |
154 | } else { |
155 | const size_t idx = cur.idx; |
156 | cur.idx++; |
157 | kv_last(stack) = cur; |
158 | kv_push(stack, ((MPToAPIObjectStackItem) { |
159 | .mobj = &cur.mobj->via.array.ptr[idx], |
160 | .aobj = &cur.aobj->data.array.items[idx], |
161 | .container = false, |
162 | })); |
163 | } |
164 | } else { |
165 | *cur.aobj = ARRAY_OBJ(((Array) { |
166 | .size = size, |
167 | .capacity = size, |
168 | .items = (size > 0 |
169 | ? xcalloc(size, sizeof(*cur.aobj->data.array.items)) |
170 | : NULL), |
171 | })); |
172 | cur.container = true; |
173 | kv_last(stack) = cur; |
174 | } |
175 | break; |
176 | } |
177 | case MSGPACK_OBJECT_MAP: { |
178 | const size_t size = cur.mobj->via.map.size; |
179 | if (cur.container) { |
180 | if (cur.idx >= size) { |
181 | (void)kv_pop(stack); |
182 | } else { |
183 | const size_t idx = cur.idx; |
184 | cur.idx++; |
185 | kv_last(stack) = cur; |
186 | const msgpack_object *const key = &cur.mobj->via.map.ptr[idx].key; |
187 | switch (key->type) { |
188 | #define ID(x) x |
189 | STR_CASE(MSGPACK_OBJECT_STR, str, key, |
190 | cur.aobj->data.dictionary.items[idx].key, ID) |
191 | STR_CASE(MSGPACK_OBJECT_BIN, bin, key, |
192 | cur.aobj->data.dictionary.items[idx].key, ID) |
193 | #undef ID |
194 | case MSGPACK_OBJECT_NIL: |
195 | case MSGPACK_OBJECT_BOOLEAN: |
196 | case MSGPACK_OBJECT_POSITIVE_INTEGER: |
197 | case MSGPACK_OBJECT_NEGATIVE_INTEGER: |
198 | #ifdef NVIM_MSGPACK_HAS_FLOAT32 |
199 | case MSGPACK_OBJECT_FLOAT32: |
200 | case MSGPACK_OBJECT_FLOAT64: |
201 | #else |
202 | case MSGPACK_OBJECT_FLOAT: |
203 | #endif |
204 | case MSGPACK_OBJECT_EXT: |
205 | case MSGPACK_OBJECT_MAP: |
206 | case MSGPACK_OBJECT_ARRAY: { |
207 | ret = false; |
208 | break; |
209 | } |
210 | } |
211 | if (ret) { |
212 | kv_push(stack, ((MPToAPIObjectStackItem) { |
213 | .mobj = &cur.mobj->via.map.ptr[idx].val, |
214 | .aobj = &cur.aobj->data.dictionary.items[idx].value, |
215 | .container = false, |
216 | })); |
217 | } |
218 | } |
219 | } else { |
220 | *cur.aobj = DICTIONARY_OBJ(((Dictionary) { |
221 | .size = size, |
222 | .capacity = size, |
223 | .items = (size > 0 |
224 | ? xcalloc(size, sizeof(*cur.aobj->data.dictionary.items)) |
225 | : NULL), |
226 | })); |
227 | cur.container = true; |
228 | kv_last(stack) = cur; |
229 | } |
230 | break; |
231 | } |
232 | case MSGPACK_OBJECT_EXT: { |
233 | switch ((ObjectType)(cur.mobj->via.ext.type + EXT_OBJECT_TYPE_SHIFT)) { |
234 | case kObjectTypeBuffer: { |
235 | cur.aobj->type = kObjectTypeBuffer; |
236 | ret = msgpack_rpc_to_buffer(cur.mobj, &cur.aobj->data.integer); |
237 | break; |
238 | } |
239 | case kObjectTypeWindow: { |
240 | cur.aobj->type = kObjectTypeWindow; |
241 | ret = msgpack_rpc_to_window(cur.mobj, &cur.aobj->data.integer); |
242 | break; |
243 | } |
244 | case kObjectTypeTabpage: { |
245 | cur.aobj->type = kObjectTypeTabpage; |
246 | ret = msgpack_rpc_to_tabpage(cur.mobj, &cur.aobj->data.integer); |
247 | break; |
248 | } |
249 | case kObjectTypeNil: |
250 | case kObjectTypeBoolean: |
251 | case kObjectTypeInteger: |
252 | case kObjectTypeFloat: |
253 | case kObjectTypeString: |
254 | case kObjectTypeArray: |
255 | case kObjectTypeDictionary: |
256 | case kObjectTypeLuaRef: { |
257 | break; |
258 | } |
259 | } |
260 | break; |
261 | } |
262 | #undef STR_CASE |
263 | } |
264 | if (!cur.container) { |
265 | (void)kv_pop(stack); |
266 | } |
267 | } |
268 | kv_destroy(stack); |
269 | return ret; |
270 | } |
271 | |
272 | static bool msgpack_rpc_to_string(const msgpack_object *const obj, |
273 | String *const arg) |
274 | FUNC_ATTR_NONNULL_ALL |
275 | { |
276 | if (obj->type == MSGPACK_OBJECT_BIN || obj->type == MSGPACK_OBJECT_STR) { |
277 | arg->data = obj->via.bin.ptr != NULL |
278 | ? xmemdupz(obj->via.bin.ptr, obj->via.bin.size) |
279 | : NULL; |
280 | arg->size = obj->via.bin.size; |
281 | return true; |
282 | } |
283 | return false; |
284 | } |
285 | |
286 | bool msgpack_rpc_to_array(const msgpack_object *const obj, Array *const arg) |
287 | FUNC_ATTR_NONNULL_ALL |
288 | { |
289 | if (obj->type != MSGPACK_OBJECT_ARRAY) { |
290 | return false; |
291 | } |
292 | |
293 | arg->size = obj->via.array.size; |
294 | arg->items = xcalloc(obj->via.array.size, sizeof(Object)); |
295 | |
296 | for (uint32_t i = 0; i < obj->via.array.size; i++) { |
297 | if (!msgpack_rpc_to_object(obj->via.array.ptr + i, &arg->items[i])) { |
298 | return false; |
299 | } |
300 | } |
301 | |
302 | return true; |
303 | } |
304 | |
305 | bool msgpack_rpc_to_dictionary(const msgpack_object *const obj, |
306 | Dictionary *const arg) |
307 | FUNC_ATTR_NONNULL_ALL |
308 | { |
309 | if (obj->type != MSGPACK_OBJECT_MAP) { |
310 | return false; |
311 | } |
312 | |
313 | arg->size = obj->via.array.size; |
314 | arg->items = xcalloc(obj->via.map.size, sizeof(KeyValuePair)); |
315 | |
316 | |
317 | for (uint32_t i = 0; i < obj->via.map.size; i++) { |
318 | if (!msgpack_rpc_to_string(&obj->via.map.ptr[i].key, |
319 | &arg->items[i].key)) { |
320 | return false; |
321 | } |
322 | |
323 | if (!msgpack_rpc_to_object(&obj->via.map.ptr[i].val, |
324 | &arg->items[i].value)) { |
325 | return false; |
326 | } |
327 | } |
328 | |
329 | return true; |
330 | } |
331 | |
332 | void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res) |
333 | FUNC_ATTR_NONNULL_ARG(2) |
334 | { |
335 | if (result) { |
336 | msgpack_pack_true(res); |
337 | } else { |
338 | msgpack_pack_false(res); |
339 | } |
340 | } |
341 | |
342 | void msgpack_rpc_from_integer(Integer result, msgpack_packer *res) |
343 | FUNC_ATTR_NONNULL_ARG(2) |
344 | { |
345 | msgpack_pack_int64(res, result); |
346 | } |
347 | |
348 | void msgpack_rpc_from_float(Float result, msgpack_packer *res) |
349 | FUNC_ATTR_NONNULL_ARG(2) |
350 | { |
351 | msgpack_pack_double(res, result); |
352 | } |
353 | |
354 | void msgpack_rpc_from_string(const String result, msgpack_packer *res) |
355 | FUNC_ATTR_NONNULL_ARG(2) |
356 | { |
357 | msgpack_pack_str(res, result.size); |
358 | if (result.size > 0) { |
359 | msgpack_pack_str_body(res, result.data, result.size); |
360 | } |
361 | } |
362 | |
363 | typedef struct { |
364 | const Object *aobj; |
365 | bool container; |
366 | size_t idx; |
367 | } APIToMPObjectStackItem; |
368 | |
369 | /// Convert type used by Nvim API to msgpack type. |
370 | /// |
371 | /// @param[in] result Object to convert. |
372 | /// @param[out] res Structure that defines where conversion results are saved. |
373 | /// |
374 | /// @return true in case of success, false otherwise. |
375 | void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) |
376 | FUNC_ATTR_NONNULL_ARG(2) |
377 | { |
378 | kvec_t(APIToMPObjectStackItem) stack = KV_INITIAL_VALUE; |
379 | kv_push(stack, ((APIToMPObjectStackItem) { &result, false, 0 })); |
380 | while (kv_size(stack)) { |
381 | APIToMPObjectStackItem cur = kv_last(stack); |
382 | STATIC_ASSERT(kObjectTypeWindow == kObjectTypeBuffer + 1 |
383 | && kObjectTypeTabpage == kObjectTypeWindow + 1, |
384 | "Buffer, window and tabpage enum items are in order" ); |
385 | switch (cur.aobj->type) { |
386 | case kObjectTypeNil: |
387 | case kObjectTypeLuaRef: { |
388 | // TODO(bfredl): could also be an error. Though kObjectTypeLuaRef |
389 | // should only appear when the caller has opted in to handle references, |
390 | // see nlua_pop_Object. |
391 | msgpack_pack_nil(res); |
392 | break; |
393 | } |
394 | case kObjectTypeBoolean: { |
395 | msgpack_rpc_from_boolean(cur.aobj->data.boolean, res); |
396 | break; |
397 | } |
398 | case kObjectTypeInteger: { |
399 | msgpack_rpc_from_integer(cur.aobj->data.integer, res); |
400 | break; |
401 | } |
402 | case kObjectTypeFloat: { |
403 | msgpack_rpc_from_float(cur.aobj->data.floating, res); |
404 | break; |
405 | } |
406 | case kObjectTypeString: { |
407 | msgpack_rpc_from_string(cur.aobj->data.string, res); |
408 | break; |
409 | } |
410 | case kObjectTypeBuffer: { |
411 | msgpack_rpc_from_buffer(cur.aobj->data.integer, res); |
412 | break; |
413 | } |
414 | case kObjectTypeWindow: { |
415 | msgpack_rpc_from_window(cur.aobj->data.integer, res); |
416 | break; |
417 | } |
418 | case kObjectTypeTabpage: { |
419 | msgpack_rpc_from_tabpage(cur.aobj->data.integer, res); |
420 | break; |
421 | } |
422 | case kObjectTypeArray: { |
423 | const size_t size = cur.aobj->data.array.size; |
424 | if (cur.container) { |
425 | if (cur.idx >= size) { |
426 | (void)kv_pop(stack); |
427 | } else { |
428 | const size_t idx = cur.idx; |
429 | cur.idx++; |
430 | kv_last(stack) = cur; |
431 | kv_push(stack, ((APIToMPObjectStackItem) { |
432 | .aobj = &cur.aobj->data.array.items[idx], |
433 | .container = false, |
434 | })); |
435 | } |
436 | } else { |
437 | msgpack_pack_array(res, size); |
438 | cur.container = true; |
439 | kv_last(stack) = cur; |
440 | } |
441 | break; |
442 | } |
443 | case kObjectTypeDictionary: { |
444 | const size_t size = cur.aobj->data.dictionary.size; |
445 | if (cur.container) { |
446 | if (cur.idx >= size) { |
447 | (void)kv_pop(stack); |
448 | } else { |
449 | const size_t idx = cur.idx; |
450 | cur.idx++; |
451 | kv_last(stack) = cur; |
452 | msgpack_rpc_from_string(cur.aobj->data.dictionary.items[idx].key, |
453 | res); |
454 | kv_push(stack, ((APIToMPObjectStackItem) { |
455 | .aobj = &cur.aobj->data.dictionary.items[idx].value, |
456 | .container = false, |
457 | })); |
458 | } |
459 | } else { |
460 | msgpack_pack_map(res, size); |
461 | cur.container = true; |
462 | kv_last(stack) = cur; |
463 | } |
464 | break; |
465 | } |
466 | } |
467 | if (!cur.container) { |
468 | (void)kv_pop(stack); |
469 | } |
470 | } |
471 | kv_destroy(stack); |
472 | } |
473 | |
474 | void msgpack_rpc_from_array(Array result, msgpack_packer *res) |
475 | FUNC_ATTR_NONNULL_ARG(2) |
476 | { |
477 | msgpack_pack_array(res, result.size); |
478 | |
479 | for (size_t i = 0; i < result.size; i++) { |
480 | msgpack_rpc_from_object(result.items[i], res); |
481 | } |
482 | } |
483 | |
484 | void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) |
485 | FUNC_ATTR_NONNULL_ARG(2) |
486 | { |
487 | msgpack_pack_map(res, result.size); |
488 | |
489 | for (size_t i = 0; i < result.size; i++) { |
490 | msgpack_rpc_from_string(result.items[i].key, res); |
491 | msgpack_rpc_from_object(result.items[i].value, res); |
492 | } |
493 | } |
494 | |
495 | /// Serializes a msgpack-rpc request or notification(id == 0) |
496 | void msgpack_rpc_serialize_request(uint32_t request_id, |
497 | const String method, |
498 | Array args, |
499 | msgpack_packer *pac) |
500 | FUNC_ATTR_NONNULL_ARG(4) |
501 | { |
502 | msgpack_pack_array(pac, request_id ? 4 : 3); |
503 | msgpack_pack_int(pac, request_id ? 0 : 2); |
504 | |
505 | if (request_id) { |
506 | msgpack_pack_uint32(pac, request_id); |
507 | } |
508 | |
509 | msgpack_rpc_from_string(method, pac); |
510 | msgpack_rpc_from_array(args, pac); |
511 | } |
512 | |
513 | /// Serializes a msgpack-rpc response |
514 | void msgpack_rpc_serialize_response(uint32_t response_id, |
515 | Error *err, |
516 | Object arg, |
517 | msgpack_packer *pac) |
518 | FUNC_ATTR_NONNULL_ARG(2, 4) |
519 | { |
520 | msgpack_pack_array(pac, 4); |
521 | msgpack_pack_int(pac, 1); |
522 | msgpack_pack_uint32(pac, response_id); |
523 | |
524 | if (ERROR_SET(err)) { |
525 | // error represented by a [type, message] array |
526 | msgpack_pack_array(pac, 2); |
527 | msgpack_rpc_from_integer(err->type, pac); |
528 | msgpack_rpc_from_string(cstr_as_string(err->msg), pac); |
529 | // Nil result |
530 | msgpack_pack_nil(pac); |
531 | } else { |
532 | // Nil error |
533 | msgpack_pack_nil(pac); |
534 | // Return value |
535 | msgpack_rpc_from_object(arg, pac); |
536 | } |
537 | } |
538 | |
539 | static bool msgpack_rpc_is_notification(msgpack_object *req) |
540 | { |
541 | return req->via.array.ptr[0].via.u64 == 2; |
542 | } |
543 | |
544 | msgpack_object *msgpack_rpc_method(msgpack_object *req) |
545 | { |
546 | msgpack_object *obj = req->via.array.ptr |
547 | + (msgpack_rpc_is_notification(req) ? 1 : 2); |
548 | return obj->type == MSGPACK_OBJECT_STR || obj->type == MSGPACK_OBJECT_BIN ? |
549 | obj : NULL; |
550 | } |
551 | |
552 | msgpack_object *msgpack_rpc_args(msgpack_object *req) |
553 | { |
554 | msgpack_object *obj = req->via.array.ptr |
555 | + (msgpack_rpc_is_notification(req) ? 2 : 3); |
556 | return obj->type == MSGPACK_OBJECT_ARRAY ? obj : NULL; |
557 | } |
558 | |
559 | static msgpack_object *msgpack_rpc_msg_id(msgpack_object *req) |
560 | { |
561 | if (msgpack_rpc_is_notification(req)) { |
562 | return NULL; |
563 | } |
564 | msgpack_object *obj = &req->via.array.ptr[1]; |
565 | return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER ? obj : NULL; |
566 | } |
567 | |
568 | MessageType msgpack_rpc_validate(uint32_t *response_id, msgpack_object *req, |
569 | Error *err) |
570 | { |
571 | *response_id = 0; |
572 | // Validate the basic structure of the msgpack-rpc payload |
573 | if (req->type != MSGPACK_OBJECT_ARRAY) { |
574 | api_set_error(err, kErrorTypeValidation, "Message is not an array" ); |
575 | return kMessageTypeUnknown; |
576 | } |
577 | |
578 | if (req->via.array.size == 0) { |
579 | api_set_error(err, kErrorTypeValidation, "Message is empty" ); |
580 | return kMessageTypeUnknown; |
581 | } |
582 | |
583 | if (req->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { |
584 | api_set_error(err, kErrorTypeValidation, "Message type must be an integer" ); |
585 | return kMessageTypeUnknown; |
586 | } |
587 | |
588 | MessageType type = (MessageType)req->via.array.ptr[0].via.u64; |
589 | if (type != kMessageTypeRequest && type != kMessageTypeNotification) { |
590 | api_set_error(err, kErrorTypeValidation, "Unknown message type" ); |
591 | return kMessageTypeUnknown; |
592 | } |
593 | |
594 | if ((type == kMessageTypeRequest && req->via.array.size != 4) |
595 | || (type == kMessageTypeNotification && req->via.array.size != 3)) { |
596 | api_set_error(err, kErrorTypeValidation, |
597 | "Request array size must be 4 (request) or 3 (notification)" ); |
598 | return type; |
599 | } |
600 | |
601 | if (type == kMessageTypeRequest) { |
602 | msgpack_object *id_obj = msgpack_rpc_msg_id(req); |
603 | if (!id_obj) { |
604 | api_set_error(err, kErrorTypeValidation, "ID must be a positive integer" ); |
605 | return type; |
606 | } |
607 | *response_id = (uint32_t)id_obj->via.u64; |
608 | } |
609 | |
610 | if (!msgpack_rpc_method(req)) { |
611 | api_set_error(err, kErrorTypeValidation, "Method must be a string" ); |
612 | return type; |
613 | } |
614 | |
615 | if (!msgpack_rpc_args(req)) { |
616 | api_set_error(err, kErrorTypeValidation, "Parameters must be an array" ); |
617 | return type; |
618 | } |
619 | |
620 | return type; |
621 | } |
622 | |