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
22static msgpack_zone zone;
23static 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
62void msgpack_rpc_helpers_init(void)
63{
64 msgpack_zone_init(&zone, 0xfff);
65 msgpack_sbuffer_init(&sbuffer);
66}
67
68HANDLE_TYPE_CONVERSION_IMPL(Buffer, buffer)
69HANDLE_TYPE_CONVERSION_IMPL(Window, window)
70HANDLE_TYPE_CONVERSION_IMPL(Tabpage, tabpage)
71
72typedef 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.
85bool 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
272static 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
286bool 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
305bool 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
332void 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
342void msgpack_rpc_from_integer(Integer result, msgpack_packer *res)
343 FUNC_ATTR_NONNULL_ARG(2)
344{
345 msgpack_pack_int64(res, result);
346}
347
348void msgpack_rpc_from_float(Float result, msgpack_packer *res)
349 FUNC_ATTR_NONNULL_ARG(2)
350{
351 msgpack_pack_double(res, result);
352}
353
354void 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
363typedef 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.
375void 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
474void 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
484void 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)
496void 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
514void 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
539static bool msgpack_rpc_is_notification(msgpack_object *req)
540{
541 return req->via.array.ptr[0].via.u64 == 2;
542}
543
544msgpack_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
552msgpack_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
559static 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
568MessageType 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