Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-core"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <json-glib/json-glib.h>
10 : :
11 : : #include "../core/valent-global.h"
12 : : #include "valent-packet.h"
13 : :
14 : :
15 [ + + ]: 15 : G_DEFINE_QUARK (valent-packet-error, valent_packet_error)
16 : :
17 : :
18 : : /**
19 : : * valent_packet_new:
20 : : * @type: a KDE Connect packet type
21 : : *
22 : : * A convenience function for creating a new KDE Connect packet with the type
23 : : * field set to @type.
24 : : *
25 : : * Returns: (transfer full): a KDE Connect packet
26 : : *
27 : : * Since: 1.0
28 : : */
29 : : JsonNode *
30 : 12 : valent_packet_new (const char *type)
31 : : {
32 : 24 : g_autoptr (JsonBuilder) builder = NULL;
33 : :
34 [ + - ]: 12 : g_return_val_if_fail (type != NULL, NULL);
35 : :
36 : 12 : builder = json_builder_new ();
37 : :
38 : 12 : json_builder_begin_object (builder);
39 : 12 : json_builder_set_member_name (builder, "id");
40 : 12 : json_builder_add_int_value (builder, 0);
41 : 12 : json_builder_set_member_name (builder, "type");
42 : 12 : json_builder_add_string_value (builder, type);
43 : 12 : json_builder_set_member_name (builder, "body");
44 : 12 : json_builder_end_object (json_builder_begin_object (builder));
45 : 12 : json_builder_end_object (builder);
46 : :
47 : 12 : return json_builder_get_root (builder);
48 : : }
49 : :
50 : :
51 : : /**
52 : : * valent_packet_init: (skip)
53 : : * @builder: a location to initialize a `JsonBuilder`
54 : : * @type: a KDE Connect packet type
55 : : *
56 : : * Initialize a [class@Json.Builder] and KDE Connect packet.
57 : : *
58 : : * Creates a new [class@Json.Builder] and initializes a packet for @type,
59 : : * leaving the builder in the `body` object. Call [func@Valent.packet_end]
60 : : * to finish the packet and get the result.
61 : : *
62 : : * ```c
63 : : * g_autoptr (JsonBuilder) builder = NULL;
64 : : * g_autoptr (JsonNode) packet = NULL;
65 : : *
66 : : * valent_packet_init (&builder, "kdeconnect.ping");
67 : : * json_builder_set_member_name (builder, "message");
68 : : * json_builder_add_string_value (builder, "Ping!");
69 : : * packet = valent_packet_end (&builder);
70 : : * ```
71 : : *
72 : : *
73 : : * Since: 1.0
74 : : */
75 : : void
76 : 154 : valent_packet_init (JsonBuilder **builder,
77 : : const char *type)
78 : : {
79 [ + - + - ]: 154 : g_return_if_fail (builder != NULL && *builder == NULL);
80 [ + - - + ]: 154 : g_return_if_fail (type != NULL && *type != '\0');
81 : :
82 : 154 : *builder = json_builder_new ();
83 : 154 : json_builder_begin_object (*builder);
84 : 154 : json_builder_set_member_name (*builder, "id");
85 : 154 : json_builder_add_int_value (*builder, 0);
86 : 154 : json_builder_set_member_name (*builder, "type");
87 : 154 : json_builder_add_string_value (*builder, type);
88 : 154 : json_builder_set_member_name (*builder, "body");
89 : :
90 : 154 : json_builder_begin_object (*builder);
91 : : }
92 : :
93 : : /**
94 : : * valent_packet_end: (skip)
95 : : * @builder: a pointer to a `JsonBuilder`
96 : : *
97 : : * Finish a packet created with [func@Valent.packet_init].
98 : : *
99 : : * This function closes the `body` and root objects, then calls
100 : : * [method@Json.Builder.get_root]. Then the reference count of @builder is
101 : : * decreased and the pointer is set to %NULL, before returning the packet.
102 : : *
103 : : * Returns: (transfer full) (nullable): a KDE Connect packet
104 : : *
105 : : * Since: 1.0
106 : : */
107 : : JsonNode *
108 : 154 : valent_packet_end (JsonBuilder **builder)
109 : : {
110 : 154 : JsonNode *ret = NULL;
111 : :
112 [ + - + - : 154 : g_return_val_if_fail (builder != NULL && JSON_IS_BUILDER (*builder), NULL);
+ - - + -
- ]
113 : :
114 : : /* Finish the `body` object and the root object */
115 : 154 : json_builder_end_object (*builder);
116 : 154 : json_builder_end_object (*builder);
117 : :
118 : 154 : ret = json_builder_get_root (*builder);
119 [ + - ]: 154 : g_clear_object (builder);
120 : :
121 : : return g_steal_pointer (&ret);
122 : : }
123 : :
124 : : /**
125 : : * valent_packet_get_id:
126 : : * @packet: a KDE Connect packet
127 : : *
128 : : * Convenience function for getting the timestamp of a KDE Connect packet.
129 : : *
130 : : * Returns: a UNIX epoch timestamp
131 : : *
132 : : * Since: 1.0
133 : : */
134 : : int64_t
135 : 1 : valent_packet_get_id (JsonNode *packet)
136 : : {
137 : 1 : JsonObject *root;
138 : 1 : JsonNode *node;
139 : :
140 [ + - ]: 1 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), 0);
141 : :
142 : 1 : root = json_node_get_object (packet);
143 : :
144 [ + - + - ]: 1 : if G_UNLIKELY ((node = json_object_get_member (root, "id")) == NULL ||
145 : : json_node_get_value_type (node) != G_TYPE_INT64)
146 : 0 : g_return_val_if_reached (0);
147 : :
148 : 1 : return json_node_get_int (node);
149 : : }
150 : :
151 : : /**
152 : : * valent_packet_get_type:
153 : : * @packet: a KDE Connect packet
154 : : *
155 : : * Convenience function for getting the capability type of a KDE Connect packet.
156 : : *
157 : : * Returns: (transfer none) (nullable): a KDE Connect capability
158 : : *
159 : : * Since: 1.0
160 : : */
161 : : const char *
162 : 281 : valent_packet_get_type (JsonNode *packet)
163 : : {
164 : 281 : JsonObject *root;
165 : 281 : JsonNode *node;
166 : :
167 [ + - ]: 281 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
168 : :
169 : 281 : root = json_node_get_object (packet);
170 : :
171 [ + - + - ]: 281 : if G_UNLIKELY ((node = json_object_get_member (root, "type")) == NULL ||
172 : : json_node_get_value_type (node) != G_TYPE_STRING)
173 : 0 : g_return_val_if_reached (NULL);
174 : :
175 : 281 : return json_node_get_string (node);
176 : : }
177 : :
178 : : /**
179 : : * valent_packet_get_body:
180 : : * @packet: a KDE Connect packet
181 : : *
182 : : * Convenience function for getting the packet body of a KDE Connect packet.
183 : : *
184 : : * Returns: (transfer none) (nullable): a `JsonObject`
185 : : *
186 : : * Since: 1.0
187 : : */
188 : : JsonObject *
189 : 407 : valent_packet_get_body (JsonNode *packet)
190 : : {
191 : 407 : JsonObject *root;
192 : 407 : JsonNode *node;
193 : :
194 [ + - ]: 407 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
195 : :
196 : 407 : root = json_node_get_object (packet);
197 : :
198 [ + - + - ]: 407 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
199 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
200 : 0 : g_return_val_if_reached (NULL);
201 : :
202 : 407 : return json_node_get_object (node);
203 : : }
204 : :
205 : : /**
206 : : * valent_packet_has_payload:
207 : : * @packet: a KDE Connect packet
208 : : *
209 : : * Return %TRUE if the packet holds valid transfer information. Payload
210 : : * information is considered invalid in the following cases:
211 : : *
212 : : * - The `payloadSize` field is present, but not a %G_TYPE_INT64
213 : : * - The `payloadTransferInfo` field is missing from the root object
214 : : * - The `payloadTransferInfo` field is not a %JSON_NODE_OBJECT
215 : : *
216 : : * Returns: %TRUE if @packet has a payload
217 : : *
218 : : * Since: 1.0
219 : : */
220 : : gboolean
221 : 43 : valent_packet_has_payload (JsonNode *packet)
222 : : {
223 : 43 : JsonObject *root;
224 : 43 : JsonNode *node;
225 : :
226 [ + - ]: 43 : g_return_val_if_fail (VALENT_IS_PACKET (packet), FALSE);
227 : :
228 : 43 : root = json_node_get_object (packet);
229 : :
230 [ + + - + ]: 80 : if ((node = json_object_get_member (root, "payloadSize")) != NULL &&
231 : 37 : json_node_get_value_type (node) != G_TYPE_INT64)
232 : : return FALSE;
233 : :
234 [ + + - + ]: 70 : if ((node = json_object_get_member (root, "payloadTransferInfo")) == NULL ||
235 : 27 : json_node_get_node_type (node) != JSON_NODE_OBJECT)
236 : 16 : return FALSE;
237 : :
238 : : return TRUE;
239 : : }
240 : :
241 : : /**
242 : : * valent_packet_get_payload_full:
243 : : * @packet: a KDE Connect packet
244 : : * @size: (out) (nullable): the payload size
245 : : * @error: (nullable): a `GError`
246 : : *
247 : : * A convenience for retrieving the `payloadTransferInfo` and `payloadSize`
248 : : * fields from @packet.
249 : : *
250 : : * If @packet is malformed or missing payload information, %NULL will be
251 : : * returned with @error set. See valent_packet_has_payload() for validation
252 : : * criteria.
253 : : *
254 : : * Returns: (transfer none) (nullable): a `JsonObject`
255 : : *
256 : : * Since: 1.0
257 : : */
258 : : JsonObject *
259 : 29 : valent_packet_get_payload_full (JsonNode *packet,
260 : : goffset *size,
261 : : GError **error)
262 : : {
263 : 29 : JsonObject *root;
264 : 29 : JsonNode *node;
265 : :
266 [ - + ]: 29 : if (!valent_packet_validate (packet, error))
267 : : return NULL;
268 : :
269 : 29 : root = json_node_get_object (packet);
270 : :
271 : : /* The documentation implies that this field could be missing or have a value
272 : : * of `-1` to indicate the length is indefinite (eg. for streaming). */
273 [ + - - + ]: 58 : if ((node = json_object_get_member (root, "payloadSize")) != NULL &&
274 : 29 : json_node_get_value_type (node) != G_TYPE_INT64)
275 : : {
276 : 0 : g_set_error_literal (error,
277 : : VALENT_PACKET_ERROR,
278 : : VALENT_PACKET_ERROR_INVALID_FIELD,
279 : : "expected \"payloadSize\" field to hold an integer");
280 : 0 : return NULL;
281 : : }
282 : :
283 [ + - ]: 29 : if (size != NULL)
284 [ + - ]: 29 : *size = node ? json_node_get_int (node) : -1;
285 : :
286 [ + - - + ]: 58 : if ((node = json_object_get_member (root, "payloadTransferInfo")) == NULL ||
287 : 29 : json_node_get_node_type (node) != JSON_NODE_OBJECT)
288 : : {
289 : 0 : g_set_error_literal (error,
290 : : VALENT_PACKET_ERROR,
291 : : node == NULL
292 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
293 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
294 : : "expected \"payloadTransferInfo\" field holding an object");
295 : 0 : return NULL;
296 : : }
297 : :
298 : 29 : return json_node_get_object (node);
299 : : }
300 : :
301 : : /**
302 : : * valent_packet_set_payload_full:
303 : : * @packet: a KDE Connect packet
304 : : * @info: (transfer full): a `JsonObject`
305 : : * @size: the payload size in bytes
306 : : *
307 : : * A convenience method for setting the `payloadTransferInfo` and `payloadSize`
308 : : * fields on @packet.
309 : : *
310 : : * Since: 1.0
311 : : */
312 : : void
313 : 1 : valent_packet_set_payload_full (JsonNode *packet,
314 : : JsonObject *info,
315 : : goffset size)
316 : : {
317 : 1 : JsonObject *root;
318 : :
319 [ + - ]: 1 : g_return_if_fail (VALENT_IS_PACKET (packet));
320 : :
321 : 1 : root = json_node_get_object (packet);
322 : :
323 : 1 : json_object_set_object_member (root, "payloadTransferInfo", info);
324 : 1 : json_object_set_int_member (root, "payloadSize", (int64_t)size);
325 : : }
326 : :
327 : : /**
328 : : * valent_packet_get_payload_info:
329 : : * @packet: a KDE Connect packet
330 : : *
331 : : * A convenience for retrieve the 'payloadTransferInfo` field from @packet.
332 : : *
333 : : * Returns: (transfer none) (nullable): a `JsonObject`
334 : : *
335 : : * Since: 1.0
336 : : */
337 : : JsonObject *
338 : 1 : valent_packet_get_payload_info (JsonNode *packet)
339 : : {
340 : 1 : JsonNode *node;
341 : :
342 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_PACKET (packet), NULL);
343 : :
344 : 1 : node = json_object_get_member (json_node_get_object (packet),
345 : : "payloadTransferInfo");
346 : :
347 [ + - + - ]: 1 : if G_UNLIKELY (node == NULL || !JSON_NODE_HOLDS_OBJECT (node))
348 : 0 : g_return_val_if_reached (NULL);
349 : :
350 : 1 : return json_node_get_object (node);
351 : : }
352 : :
353 : : /**
354 : : * valent_packet_set_payload_info:
355 : : * @packet: a KDE Connect packet
356 : : * @info: (transfer full): a `JsonObject`
357 : : *
358 : : * A convenience method for setting the `payloadTransferInfo` field on @packet.
359 : : *
360 : : * Since: 1.0
361 : : */
362 : : void
363 : 29 : valent_packet_set_payload_info (JsonNode *packet,
364 : : JsonObject *info)
365 : : {
366 : 29 : JsonObject *root;
367 : :
368 [ + - ]: 29 : g_return_if_fail (VALENT_IS_PACKET (packet));
369 [ - + ]: 29 : g_return_if_fail (info != NULL);
370 : :
371 : 29 : root = json_node_get_object (packet);
372 : :
373 : 29 : json_object_set_object_member (root, "payloadTransferInfo", info);
374 : : }
375 : :
376 : : /**
377 : : * valent_packet_get_payload_size:
378 : : * @packet: a KDE Connect packet
379 : : *
380 : : * Get the `payloadSize` field of @packet in bytes.
381 : : *
382 : : * Returns: the payload size
383 : : *
384 : : * Since: 1.0
385 : : */
386 : : goffset
387 : 44 : valent_packet_get_payload_size (JsonNode *packet)
388 : : {
389 : 44 : JsonObject *root;
390 : 44 : JsonNode *node;
391 : :
392 [ + - ]: 44 : g_return_val_if_fail (VALENT_IS_PACKET (packet), 0);
393 : :
394 : 44 : root = json_node_get_object (packet);
395 : 44 : node = json_object_get_member (root, "payloadSize");
396 : :
397 [ + - - + ]: 88 : if ((node = json_object_get_member (root, "payloadSize")) != NULL &&
398 : 44 : json_node_get_value_type (node) != G_TYPE_INT64)
399 : 0 : g_return_val_if_reached (-1);
400 : :
401 : 44 : return node ? json_node_get_int (node) : -1;
402 : : }
403 : :
404 : : /**
405 : : * valent_packet_set_payload_size:
406 : : * @packet: a KDE Connect packet
407 : : * @size: the payload size in bytes
408 : : *
409 : : * Set the `payloadSize` field of @packet to @size.
410 : : *
411 : : * Since: 1.0
412 : : */
413 : : void
414 : 38 : valent_packet_set_payload_size (JsonNode *packet,
415 : : goffset size)
416 : : {
417 : 38 : JsonObject *root;
418 : :
419 [ + - ]: 38 : g_return_if_fail (VALENT_IS_PACKET (packet));
420 [ - + ]: 38 : g_return_if_fail (size >= -1);
421 : :
422 : 38 : root = json_node_get_object (packet);
423 : :
424 : 38 : json_object_set_int_member (root, "payloadSize", (int64_t)size);
425 : : }
426 : :
427 : : /**
428 : : * valent_packet_check_field:
429 : : * @packet: a KDE Connect packet
430 : : * @field: (not nullable): field name
431 : : *
432 : : * Check @packet for @field and return %TRUE if present, with two exceptions:
433 : : *
434 : : * 1. If @field is a %G_TYPE_BOOLEAN, its value is returned
435 : : * 2. If @field is a %G_TYPE_STRING, %FALSE is returned if the string is empty.
436 : : *
437 : : * Returns: %TRUE, or %FALSE on failure
438 : : *
439 : : * Since: 1.0
440 : : */
441 : : gboolean
442 : 126 : valent_packet_check_field (JsonNode *packet,
443 : : const char *field)
444 : : {
445 : 126 : JsonObject *root;
446 : 126 : JsonObject *body;
447 : 126 : JsonNode *node;
448 : :
449 [ + - ]: 126 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
450 [ + - - + ]: 126 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
451 : :
452 : 126 : root = json_node_get_object (packet);
453 : :
454 [ + - + - ]: 126 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
455 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
456 : 0 : return FALSE;
457 : :
458 : 126 : body = json_node_get_object (node);
459 : :
460 [ + + ]: 126 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL)
461 : : return FALSE;
462 : :
463 [ + + ]: 46 : if (json_node_get_value_type (node) == G_TYPE_BOOLEAN)
464 : 33 : return json_node_get_boolean (node);
465 : :
466 [ + - ]: 13 : if (json_node_get_value_type (node) == G_TYPE_STRING)
467 : 13 : return json_node_get_string (node)[0] != '\0';
468 : :
469 : : return TRUE;
470 : : }
471 : :
472 : : /**
473 : : * valent_packet_get_boolean:
474 : : * @packet: a KDE Connect packet
475 : : * @field: (not nullable): field name
476 : : * @value: (out) (nullable): a boolean
477 : : *
478 : : * Lookup @field in the body of @packet and assign it to @value.
479 : : *
480 : : * If @field is not found or it is not a boolean, %FALSE will be returned and
481 : : * @value will not be set.
482 : : *
483 : : * Returns: %TRUE, or %FALSE on failure
484 : : *
485 : : * Since: 1.0
486 : : */
487 : : gboolean
488 : 44 : valent_packet_get_boolean (JsonNode *packet,
489 : : const char *field,
490 : : gboolean *value)
491 : : {
492 : 44 : JsonObject *root, *body;
493 : 44 : JsonNode *node;
494 : :
495 [ + - ]: 44 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
496 [ + - - + ]: 44 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
497 : :
498 : 44 : root = json_node_get_object (packet);
499 : :
500 [ + - + - ]: 44 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
501 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
502 : 0 : return FALSE;
503 : :
504 : 44 : body = json_node_get_object (node);
505 : :
506 [ + + + - ]: 44 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
507 : : json_node_get_value_type (node) != G_TYPE_BOOLEAN)
508 : 16 : return FALSE;
509 : :
510 [ + - ]: 28 : if (value)
511 : 28 : *value = json_node_get_boolean (node);
512 : :
513 : : return TRUE;
514 : : }
515 : :
516 : : /**
517 : : * valent_packet_get_double:
518 : : * @packet: a KDE Connect packet
519 : : * @field: (not nullable): field name
520 : : * @value: (out) (nullable): a double
521 : : *
522 : : * Lookup @field in the body of @packet and assign it to @value.
523 : : *
524 : : * If @field is not found or it is not a double, %FALSE will be returned and
525 : : * @value will not be set.
526 : : *
527 : : * Returns: %TRUE, or %FALSE on failure
528 : : *
529 : : * Since: 1.0
530 : : */
531 : : gboolean
532 : 5 : valent_packet_get_double (JsonNode *packet,
533 : : const char *field,
534 : : double *value)
535 : : {
536 : 5 : JsonObject *root, *body;
537 : 5 : JsonNode *node;
538 : :
539 [ + - ]: 5 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
540 [ + - - + ]: 5 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
541 : :
542 : 5 : root = json_node_get_object (packet);
543 : :
544 [ + - + - ]: 5 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
545 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
546 : 0 : return FALSE;
547 : :
548 : 5 : body = json_node_get_object (node);
549 : :
550 [ + - + - ]: 5 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
551 : : json_node_get_value_type (node) != G_TYPE_DOUBLE)
552 : 0 : return FALSE;
553 : :
554 [ + - ]: 5 : if (value)
555 : 5 : *value = json_node_get_double (node);
556 : :
557 : : return TRUE;
558 : : }
559 : :
560 : : /**
561 : : * valent_packet_get_int:
562 : : * @packet: a KDE Connect packet
563 : : * @field: (not nullable): field name
564 : : * @value: (out) (nullable): an int64
565 : : *
566 : : * Lookup @field in the body of @packet and assign it to @value.
567 : : *
568 : : * If @field is not found or it is not an integer, %FALSE will be returned and
569 : : * @value will not be set.
570 : : *
571 : : * Returns: %TRUE, or %FALSE on failure
572 : : *
573 : : * Since: 1.0
574 : : */
575 : : gboolean
576 : 128 : valent_packet_get_int (JsonNode *packet,
577 : : const char *field,
578 : : int64_t *value)
579 : : {
580 : 128 : JsonObject *root, *body;
581 : 128 : JsonNode *node;
582 : :
583 [ + - ]: 128 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
584 [ + - - + ]: 128 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
585 : :
586 : 128 : root = json_node_get_object (packet);
587 : :
588 [ + - + - ]: 128 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
589 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
590 : 0 : return FALSE;
591 : :
592 : 128 : body = json_node_get_object (node);
593 : :
594 [ + + + - ]: 128 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
595 : : json_node_get_value_type (node) != G_TYPE_INT64)
596 : 63 : return FALSE;
597 : :
598 [ + + ]: 65 : if (value)
599 : 60 : *value = json_node_get_int (node);
600 : :
601 : : return TRUE;
602 : : }
603 : :
604 : : /**
605 : : * valent_packet_get_string:
606 : : * @packet: a KDE Connect packet
607 : : * @field: (not nullable): field name
608 : : * @value: (out) (nullable): a string
609 : : *
610 : : * Lookup @field in the body of @packet and assign it to @value.
611 : : *
612 : : * If @field is not found or it is not a non-empty string, %FALSE will be
613 : : * returned and @value will not be set.
614 : : *
615 : : * Returns: %TRUE, or %FALSE on failure
616 : : *
617 : : * Since: 1.0
618 : : */
619 : : gboolean
620 : 825 : valent_packet_get_string (JsonNode *packet,
621 : : const char *field,
622 : : const char **value)
623 : : {
624 : 825 : JsonObject *root, *body;
625 : 825 : JsonNode *node;
626 : 825 : const char *string;
627 : :
628 [ + - ]: 825 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
629 [ + - - + ]: 825 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
630 : :
631 : 825 : root = json_node_get_object (packet);
632 : :
633 [ + - + - ]: 825 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
634 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
635 : 0 : return FALSE;
636 : :
637 : 825 : body = json_node_get_object (node);
638 : :
639 [ + + + - ]: 825 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
640 : : json_node_get_value_type (node) != G_TYPE_STRING)
641 : 43 : return FALSE;
642 : :
643 : 782 : string = json_node_get_string (node);
644 : :
645 [ - + ]: 782 : if G_UNLIKELY (*string == '\0')
646 : : return FALSE;
647 : :
648 [ + + ]: 782 : if (value)
649 : 779 : *value = string;
650 : :
651 : : return TRUE;
652 : : }
653 : :
654 : : /**
655 : : * valent_packet_get_array:
656 : : * @packet: a KDE Connect packet
657 : : * @field: (not nullable): field name
658 : : * @value: (out) (nullable): a `JsonArray`
659 : : *
660 : : * Lookup @field in the body of @packet and assign it to @value.
661 : : *
662 : : * If @field is not found or it is not a `JsonArray`, %FALSE will be returned and
663 : : * @value will not be set.
664 : : *
665 : : * Returns: %TRUE, or %FALSE on failure
666 : : *
667 : : * Since: 1.0
668 : : */
669 : : gboolean
670 : 25 : valent_packet_get_array (JsonNode *packet,
671 : : const char *field,
672 : : JsonArray **value)
673 : : {
674 : 25 : JsonObject *root, *body;
675 : 25 : JsonNode *node;
676 : :
677 [ + - ]: 25 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
678 [ + - - + ]: 25 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
679 : :
680 : 25 : root = json_node_get_object (packet);
681 : :
682 [ + - + - ]: 25 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
683 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
684 : 0 : return FALSE;
685 : :
686 : 25 : body = json_node_get_object (node);
687 : :
688 [ + + + - ]: 25 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
689 : : json_node_get_node_type (node) != JSON_NODE_ARRAY)
690 : 8 : return FALSE;
691 : :
692 [ + - ]: 17 : if (value)
693 : 17 : *value = json_node_get_array (node);
694 : :
695 : : return TRUE;
696 : : }
697 : :
698 : : /**
699 : : * valent_packet_get_object:
700 : : * @packet: a KDE Connect packet
701 : : * @field: (not nullable): field name
702 : : * @value: (out) (nullable): a `JsonObject`
703 : : *
704 : : * Lookup @field in the body of @packet and assign it to @value.
705 : : *
706 : : * If @field is not found or it is not a `JsonObject`, %FALSE will be returned
707 : : * and @value will not be set.
708 : : *
709 : : * Returns: %TRUE, or %FALSE on failure
710 : : *
711 : : * Since: 1.0
712 : : */
713 : : gboolean
714 : 14 : valent_packet_get_object (JsonNode *packet,
715 : : const char *field,
716 : : JsonObject **value)
717 : : {
718 : 14 : JsonObject *root, *body;
719 : 14 : JsonNode *node;
720 : :
721 [ + - ]: 14 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
722 [ + - - + ]: 14 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
723 : :
724 : 14 : root = json_node_get_object (packet);
725 : :
726 [ + - + - ]: 14 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
727 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
728 : 0 : return FALSE;
729 : :
730 : 14 : body = json_node_get_object (node);
731 : :
732 [ + - + - ]: 14 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
733 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
734 : 0 : return FALSE;
735 : :
736 [ + - ]: 14 : if (value)
737 : 14 : *value = json_node_get_object (node);
738 : :
739 : : return TRUE;
740 : : }
741 : :
742 : : /**
743 : : * valent_packet_dup_strv:
744 : : * @packet: a KDE Connect packet
745 : : * @field: (not nullable): field name
746 : : *
747 : : * Lookup @field in the body of @packet and return a newly allocated list of
748 : : * strings.
749 : : *
750 : : * If @field is not found, it is not a `JsonArray` or any of its elements are not
751 : : * strings, %NULL will be returned.
752 : : *
753 : : * Returns: (transfer full) (nullable) (array zero-terminated=1): a list of strings
754 : : *
755 : : * Since: 1.0
756 : : */
757 : : GStrv
758 : 359 : valent_packet_dup_strv (JsonNode *packet,
759 : : const char *field)
760 : : {
761 : 359 : JsonObject *root, *body;
762 : 359 : JsonNode *node;
763 : 359 : JsonArray *array;
764 : 718 : g_auto (GStrv) strv = NULL;
765 : 359 : unsigned int n_strings;
766 : :
767 [ + - ]: 359 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
768 [ + - - + ]: 359 : g_return_val_if_fail (field != NULL && *field != '\0', NULL);
769 : :
770 : : #ifndef __clang_analyzer__
771 : 359 : root = json_node_get_object (packet);
772 : :
773 [ + - + - ]: 359 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
774 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
775 : 0 : return NULL;
776 : :
777 : 359 : body = json_node_get_object (node);
778 : :
779 [ + - + - ]: 359 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
780 : : json_node_get_node_type (node) != JSON_NODE_ARRAY)
781 : 0 : return NULL;
782 : :
783 : 359 : array = json_node_get_array (node);
784 : 359 : n_strings = json_array_get_length (array);
785 [ - + ]: 359 : strv = g_new0 (char *, n_strings + 1);
786 : :
787 [ + + ]: 1099 : for (unsigned int i = 0; i < n_strings; i++)
788 : : {
789 : 740 : JsonNode *element = json_array_get_element (array, i);
790 : :
791 [ + - ]: 740 : if G_UNLIKELY (json_node_get_value_type (element) != G_TYPE_STRING)
792 : : return NULL;
793 : :
794 : 740 : strv[i] = json_node_dup_string (element);
795 : : }
796 : : #endif /* __clang_analyzer__ */
797 : :
798 : : return g_steal_pointer (&strv);
799 : : }
800 : :
801 : : /**
802 : : * valent_packet_validate:
803 : : * @packet: (nullable): a KDE Connect packet
804 : : * @error: (nullable): a `GError`
805 : : *
806 : : * Check if @packet is a well-formed KDE Connect packet.
807 : : *
808 : : * Returns: %TRUE if @packet is valid, or %FALSE with @error set
809 : : *
810 : : * Since: 1.0
811 : : */
812 : : gboolean
813 : 643 : valent_packet_validate (JsonNode *packet,
814 : : GError **error)
815 : : {
816 : 643 : JsonObject *root;
817 : 643 : JsonNode *node;
818 : :
819 [ + + ]: 643 : if G_UNLIKELY (packet == NULL)
820 : : {
821 : 2 : g_set_error_literal (error,
822 : : VALENT_PACKET_ERROR,
823 : : VALENT_PACKET_ERROR_INVALID_DATA,
824 : : "packet is NULL");
825 : 2 : return FALSE;
826 : : }
827 : :
828 [ + + ]: 641 : if G_UNLIKELY (!JSON_NODE_HOLDS_OBJECT (packet))
829 : : {
830 : 2 : g_set_error_literal (error,
831 : : VALENT_PACKET_ERROR,
832 : : VALENT_PACKET_ERROR_MALFORMED,
833 : : "expected the root element to be an object");
834 : 2 : return FALSE;
835 : : }
836 : :
837 : 639 : root = json_node_get_object (packet);
838 : :
839 : : /* TODO: kdeconnect-kde stringifies this in identity packets
840 : : * https://invent.kde.org/network/kdeconnect-kde/-/merge_requests/380 */
841 [ + + + + : 639 : if G_UNLIKELY ((node = json_object_get_member (root, "id")) == NULL ||
+ - ]
842 : : (json_node_get_value_type (node) != G_TYPE_INT64 &&
843 : : json_node_get_value_type (node) != G_TYPE_STRING))
844 : : {
845 [ + + ]: 3 : g_set_error_literal (error,
846 : : VALENT_PACKET_ERROR,
847 : : node == NULL
848 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
849 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
850 : : "expected \"id\" field holding an integer or string");
851 : 2 : return FALSE;
852 : : }
853 : :
854 [ + + + + ]: 637 : if G_UNLIKELY ((node = json_object_get_member (root, "type")) == NULL ||
855 : : json_node_get_value_type (node) != G_TYPE_STRING)
856 : : {
857 [ + + ]: 3 : g_set_error_literal (error,
858 : : VALENT_PACKET_ERROR,
859 : : node == NULL
860 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
861 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
862 : : "expected \"type\" field holding a string");
863 : 2 : return FALSE;
864 : : }
865 : :
866 [ + + + + ]: 635 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
867 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
868 : : {
869 [ + + ]: 3 : g_set_error_literal (error,
870 : : VALENT_PACKET_ERROR,
871 : : node == NULL
872 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
873 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
874 : : "expected \"body\" field holding an object");
875 : 2 : return FALSE;
876 : : }
877 : :
878 : : /* These two are optional, but have defined value types */
879 [ + + + + ]: 633 : if G_UNLIKELY ((node = json_object_get_member (root, "payloadSize")) != NULL &&
880 : : json_node_get_value_type (node) != G_TYPE_INT64)
881 : : {
882 : 1 : g_set_error_literal (error,
883 : : VALENT_PACKET_ERROR,
884 : : VALENT_PACKET_ERROR_INVALID_FIELD,
885 : : "expected \"payloadSize\" field to hold an integer");
886 : 1 : return FALSE;
887 : : }
888 : :
889 [ + + + + ]: 632 : if G_UNLIKELY ((node = json_object_get_member (root, "payloadTransferInfo")) != NULL &&
890 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
891 : : {
892 : 1 : g_set_error_literal (error,
893 : : VALENT_PACKET_ERROR,
894 : : VALENT_PACKET_ERROR_INVALID_FIELD,
895 : : "expected \"payloadTransferInfo\" field to hold an object");
896 : 1 : return FALSE;
897 : : }
898 : :
899 : : return TRUE;
900 : : }
901 : :
902 : : /**
903 : : * valent_packet_from_stream:
904 : : * @stream: a `GInputStream`
905 : : * @max_len: the maximum number bytes to read, or `-1` for no limit
906 : : * @cancellable: (nullable): a `GCancellable`
907 : : * @error: (nullable): a `GError`
908 : : *
909 : : * Read a KDE Connect packet from an input stream.
910 : : *
911 : : * If reading fails or the packet does not conform to the minimum structure of
912 : : * a KDE Connect packet, %NULL will be returned with @error set.
913 : : *
914 : : * If @max_len is greater than `-1`, then at most @max_len bytes will be read.
915 : : * If @max_len bytes are read without encountering a line-feed character, %NULL
916 : : * will be returned with @error set to %G_IO_ERROR_MESSAGE_TOO_LARGE.
917 : : *
918 : : * Returns: (transfer full): a KDE Connect packet, or %NULL with @error set.
919 : : *
920 : : * Since: 1.0
921 : : */
922 : : JsonNode *
923 : 14 : valent_packet_from_stream (GInputStream *stream,
924 : : gssize max_len,
925 : : GCancellable *cancellable,
926 : : GError **error)
927 : : {
928 : 28 : g_autoptr (JsonParser) parser = NULL;
929 [ + + ]: 14 : g_autoptr (JsonNode) packet = NULL;
930 [ - + ]: 14 : g_autofree char *line = NULL;
931 : 14 : gssize count = 0;
932 : 14 : gssize size = 4096;
933 : :
934 [ + - + - : 14 : g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+ - - + ]
935 [ + + + - : 14 : g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- + - - ]
936 [ + - - + ]: 14 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
937 : :
938 : : #ifndef __clang_analyzer__
939 [ + + ]: 14 : if (max_len < 0)
940 : 10 : max_len = G_MAXSSIZE;
941 : :
942 : 14 : line = g_malloc0 (size);
943 : :
944 : 6498 : while (TRUE)
945 : : {
946 : 6498 : gssize read = 0;
947 : :
948 [ + + ]: 6498 : if G_UNLIKELY (count == max_len)
949 : : {
950 : 1 : g_set_error (error,
951 : : G_IO_ERROR,
952 : : G_IO_ERROR_MESSAGE_TOO_LARGE,
953 : : "Packet too large");
954 : 1 : return NULL;
955 : : }
956 : :
957 [ + + ]: 6497 : if G_UNLIKELY (count == size)
958 : : {
959 : 1 : size = MIN (size * 2, max_len);
960 : 1 : line = g_realloc (line, size);
961 : : }
962 : :
963 : 12994 : read = g_input_stream_read (stream,
964 : 6497 : line + count,
965 : : 1,
966 : : cancellable,
967 : : error);
968 : :
969 [ + + ]: 6497 : if (read > 0)
970 : 6493 : count += read;
971 [ + + ]: 4 : else if (read == 0)
972 : : break;
973 : : else
974 : : return NULL;
975 : :
976 [ + + + + ]: 6493 : if G_UNLIKELY (line[count - 1] == '\n')
977 : : break;
978 : : }
979 : :
980 : 12 : parser = json_parser_new_immutable ();
981 : :
982 [ + + ]: 12 : if (!json_parser_load_from_data (parser, line, count, error))
983 : : return NULL;
984 : :
985 : 11 : packet = json_parser_steal_root (parser);
986 : :
987 [ + + ]: 11 : if (!valent_packet_validate (packet, error))
988 : 2 : return NULL;
989 : : #endif /* __clang_analyzer__ */
990 : :
991 : : return g_steal_pointer (&packet);
992 : : }
993 : :
994 : : /**
995 : : * valent_packet_to_stream:
996 : : * @stream: a `GOutputStream`
997 : : * @packet: a KDE Connect packet
998 : : * @cancellable: (nullable): a `GCancellable`
999 : : * @error: (nullable): a `GError`
1000 : : *
1001 : : * A convenience function for writing a packet to a connection.
1002 : : *
1003 : : * Returns: %TRUE if successful, or %FALSE with @error set
1004 : : *
1005 : : * Since: 1.0
1006 : : */
1007 : : gboolean
1008 : 299 : valent_packet_to_stream (GOutputStream *stream,
1009 : : JsonNode *packet,
1010 : : GCancellable *cancellable,
1011 : : GError **error)
1012 : : {
1013 : 598 : g_autoptr (JsonGenerator) generator = NULL;
1014 : 299 : JsonObject *root;
1015 [ + - ]: 299 : g_autofree char *packet_str = NULL;
1016 : 299 : size_t packet_len;
1017 : 299 : size_t n_written;
1018 : :
1019 [ + - + - : 299 : g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
+ - - + ]
1020 [ + + + - : 299 : g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
- + - - ]
1021 [ + - - + ]: 299 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1022 : :
1023 [ - + ]: 299 : if (!valent_packet_validate (packet, error))
1024 : : return FALSE;
1025 : :
1026 : : /* Timestamp the packet (UNIX Epoch ms) */
1027 : 299 : root = json_node_get_object (packet);
1028 : 299 : json_object_set_int_member (root, "id", valent_timestamp_ms ());
1029 : :
1030 : : /* Serialize the packet and replace the trailing NULL with an LF */
1031 : 299 : generator = json_generator_new ();
1032 : 299 : json_generator_set_root (generator, packet);
1033 : 299 : packet_str = json_generator_to_data (generator, &packet_len);
1034 : 299 : packet_str[packet_len++] = '\n';
1035 : :
1036 [ + + ]: 299 : if (!g_output_stream_write_all (stream,
1037 : : packet_str,
1038 : : packet_len,
1039 : : &n_written,
1040 : : cancellable,
1041 : : error))
1042 : : return FALSE;
1043 : :
1044 [ - + ]: 294 : if (n_written != packet_len)
1045 : : {
1046 : 0 : g_set_error (error,
1047 : : G_IO_ERROR,
1048 : : G_IO_ERROR_CONNECTION_CLOSED,
1049 : : "Channel is closed");
1050 : 0 : return FALSE;
1051 : : }
1052 : :
1053 : : return TRUE;
1054 : : }
1055 : :
1056 : : /**
1057 : : * valent_packet_serialize:
1058 : : * @packet: a complete KDE Connect packet
1059 : : *
1060 : : * Convenience function that updates the timestamp of a packet before returning
1061 : : * a serialized string with newline ending, ready to be written to a stream.
1062 : : *
1063 : : * Returns: (transfer full) (nullable): the serialized packet.
1064 : : *
1065 : : * Since: 1.0
1066 : : */
1067 : : char *
1068 : 15 : valent_packet_serialize (JsonNode *packet)
1069 : : {
1070 : 30 : g_autoptr (JsonGenerator) generator = NULL;
1071 : 15 : JsonObject *root;
1072 [ + - ]: 15 : g_autofree char *packet_str = NULL;
1073 : :
1074 [ + - ]: 15 : g_return_val_if_fail (VALENT_IS_PACKET (packet), NULL);
1075 : :
1076 : : /* Timestamp the packet (UNIX Epoch ms) */
1077 : 15 : root = json_node_get_object (packet);
1078 : 15 : json_object_set_int_member (root, "id", valent_timestamp_ms ());
1079 : :
1080 : : /* Stringify the packet and return a newline-terminated string */
1081 : 15 : generator = json_generator_new ();
1082 : 15 : json_generator_set_root (generator, packet);
1083 : 15 : packet_str = json_generator_to_data (generator, NULL);
1084 : :
1085 : 15 : return g_strconcat (packet_str, "\n", NULL);
1086 : : }
1087 : :
1088 : : /**
1089 : : * valent_packet_deserialize:
1090 : : * @json: a complete KDE Connect packet
1091 : : * @error: (nullable): a `GError`
1092 : : *
1093 : : * Convenience function that deserializes a KDE Connect packet from a string
1094 : : * with basic validation. If @str is empty, this function will return %NULL.
1095 : : *
1096 : : * If parsing or validation fails, @error will be set and %NULL returned.
1097 : : *
1098 : : * Returns: (transfer full) (nullable): a KDE Connect packet
1099 : : *
1100 : : * Since: 1.0
1101 : : */
1102 : : JsonNode *
1103 : 294 : valent_packet_deserialize (const char *json,
1104 : : GError **error)
1105 : : {
1106 : 588 : g_autoptr (JsonParser) parser = NULL;
1107 [ + - ]: 294 : g_autoptr (JsonNode) packet = NULL;
1108 : :
1109 [ + - ]: 294 : g_return_val_if_fail (json != NULL, NULL);
1110 [ + - - + ]: 294 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1111 : :
1112 : 294 : parser = json_parser_new_immutable ();
1113 : :
1114 [ + - ]: 294 : if (!json_parser_load_from_data (parser, json, -1, error))
1115 : : return NULL;
1116 : :
1117 [ + - ]: 294 : if ((packet = json_parser_steal_root (parser)) == NULL)
1118 : : return NULL;
1119 : :
1120 [ - + ]: 294 : if (!valent_packet_validate (packet, error))
1121 : 0 : return NULL;
1122 : :
1123 : : return g_steal_pointer (&packet);
1124 : : }
1125 : :
|