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 [ + + ]: 13 : 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 : 15 : valent_packet_new (const char *type)
31 : : {
32 : 30 : g_autoptr (JsonBuilder) builder = NULL;
33 : :
34 [ - + ]: 15 : g_return_val_if_fail (type != NULL, NULL);
35 : :
36 : 15 : builder = json_builder_new ();
37 : :
38 : 15 : json_builder_begin_object (builder);
39 : 15 : json_builder_set_member_name (builder, "id");
40 : 15 : json_builder_add_int_value (builder, 0);
41 : 15 : json_builder_set_member_name (builder, "type");
42 : 15 : json_builder_add_string_value (builder, type);
43 : 15 : json_builder_set_member_name (builder, "body");
44 : 15 : json_builder_end_object (json_builder_begin_object (builder));
45 : 15 : json_builder_end_object (builder);
46 : :
47 : 15 : 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 : 163 : valent_packet_init (JsonBuilder **builder,
77 : : const char *type)
78 : : {
79 [ + - + - ]: 163 : g_return_if_fail (builder != NULL && *builder == NULL);
80 [ + - + - ]: 163 : g_return_if_fail (type != NULL && *type != '\0');
81 : :
82 : 163 : *builder = json_builder_new ();
83 : 163 : json_builder_begin_object (*builder);
84 : 163 : json_builder_set_member_name (*builder, "id");
85 : 163 : json_builder_add_int_value (*builder, 0);
86 : 163 : json_builder_set_member_name (*builder, "type");
87 : 163 : json_builder_add_string_value (*builder, type);
88 : 163 : json_builder_set_member_name (*builder, "body");
89 : :
90 : 163 : 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 : 163 : valent_packet_end (JsonBuilder **builder)
109 : : {
110 : 163 : JsonNode *ret = NULL;
111 : :
112 [ + - + - : 163 : g_return_val_if_fail (builder != NULL && JSON_IS_BUILDER (*builder), NULL);
+ - - + -
- ]
113 : :
114 : : /* Finish the `body` object and the root object */
115 : 163 : json_builder_end_object (*builder);
116 : 163 : json_builder_end_object (*builder);
117 : :
118 : 163 : ret = json_builder_get_root (*builder);
119 [ + - ]: 163 : 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 : 292 : valent_packet_get_type (JsonNode *packet)
163 : : {
164 : 292 : JsonObject *root;
165 : 292 : JsonNode *node;
166 : :
167 [ - + ]: 292 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
168 : :
169 : 292 : root = json_node_get_object (packet);
170 : :
171 [ + - - + ]: 292 : 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 : 292 : 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 : 597 : valent_packet_get_body (JsonNode *packet)
190 : : {
191 : 597 : JsonObject *root;
192 : 597 : JsonNode *node;
193 : :
194 [ - + ]: 597 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
195 : :
196 : 597 : root = json_node_get_object (packet);
197 : :
198 [ + - - + ]: 597 : 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 : 597 : 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 : 44 : valent_packet_has_payload (JsonNode *packet)
222 : : {
223 : 44 : JsonObject *root;
224 : 44 : JsonNode *node;
225 : :
226 [ - + ]: 44 : g_return_val_if_fail (VALENT_IS_PACKET (packet), FALSE);
227 : :
228 : 44 : root = json_node_get_object (packet);
229 : :
230 [ + + - + ]: 81 : 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 [ + + - + ]: 71 : if ((node = json_object_get_member (root, "payloadTransferInfo")) == NULL ||
235 : 27 : json_node_get_node_type (node) != JSON_NODE_OBJECT)
236 : 17 : 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 : 128 : valent_packet_check_field (JsonNode *packet,
443 : : const char *field)
444 : : {
445 : 128 : JsonObject *root;
446 : 128 : JsonObject *body;
447 : 128 : JsonNode *node;
448 : :
449 [ - + ]: 128 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
450 [ + - + - ]: 128 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
451 : :
452 : 128 : root = json_node_get_object (packet);
453 : :
454 [ + - - + ]: 128 : 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 : 128 : body = json_node_get_object (node);
459 : :
460 [ + + ]: 128 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL)
461 : : return FALSE;
462 : :
463 [ + + ]: 47 : if (json_node_get_value_type (node) == G_TYPE_BOOLEAN)
464 : 34 : 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 : 47 : valent_packet_get_boolean (JsonNode *packet,
489 : : const char *field,
490 : : gboolean *value)
491 : : {
492 : 47 : JsonObject *root, *body;
493 : 47 : JsonNode *node;
494 : :
495 [ - + ]: 47 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
496 [ + - + - ]: 47 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
497 : :
498 : 47 : root = json_node_get_object (packet);
499 : :
500 [ + - - + ]: 47 : 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 : 47 : body = json_node_get_object (node);
505 : :
506 [ + + - + ]: 47 : 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 [ + - ]: 31 : if (value)
511 : 31 : *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 : 312 : valent_packet_get_int (JsonNode *packet,
577 : : const char *field,
578 : : int64_t *value)
579 : : {
580 : 312 : JsonObject *root, *body;
581 : 312 : JsonNode *node;
582 : :
583 [ - + ]: 312 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
584 [ + - + - ]: 312 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
585 : :
586 : 312 : root = json_node_get_object (packet);
587 : :
588 [ + - - + ]: 312 : 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 : 312 : body = json_node_get_object (node);
593 : :
594 [ + + - + ]: 312 : 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 [ + + ]: 249 : if (value)
599 : 244 : *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 : 44 : return FALSE;
642 : :
643 : 781 : string = json_node_get_string (node);
644 : :
645 [ - + ]: 781 : if G_UNLIKELY (*string == '\0')
646 : : return FALSE;
647 : :
648 [ + + ]: 781 : if (value)
649 : 778 : *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 : 26 : valent_packet_get_array (JsonNode *packet,
671 : : const char *field,
672 : : JsonArray **value)
673 : : {
674 : 26 : JsonObject *root, *body;
675 : 26 : JsonNode *node;
676 : :
677 [ - + ]: 26 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), FALSE);
678 [ + - + - ]: 26 : g_return_val_if_fail (field != NULL && *field != '\0', FALSE);
679 : :
680 : 26 : root = json_node_get_object (packet);
681 : :
682 [ + - - + ]: 26 : 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 : 26 : body = json_node_get_object (node);
687 : :
688 [ + + - + ]: 26 : if G_UNLIKELY ((node = json_object_get_member (body, field)) == NULL ||
689 : : json_node_get_node_type (node) != JSON_NODE_ARRAY)
690 : 9 : 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 : 353 : valent_packet_dup_strv (JsonNode *packet,
759 : : const char *field)
760 : : {
761 : 353 : JsonObject *root, *body;
762 : 353 : JsonNode *node;
763 : 353 : JsonArray *array;
764 : 706 : g_auto (GStrv) strv = NULL;
765 : 353 : unsigned int n_strings;
766 : :
767 [ - + ]: 353 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (packet), NULL);
768 [ + - + - ]: 353 : g_return_val_if_fail (field != NULL && *field != '\0', NULL);
769 : :
770 : : #ifndef __clang_analyzer__
771 : 353 : root = json_node_get_object (packet);
772 : :
773 [ + - - + ]: 353 : 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 : 353 : body = json_node_get_object (node);
778 : :
779 [ + - - + ]: 353 : 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 : 353 : array = json_node_get_array (node);
784 : 353 : n_strings = json_array_get_length (array);
785 [ - + ]: 353 : strv = g_new0 (char *, n_strings + 1);
786 : :
787 [ + + ]: 1081 : for (unsigned int i = 0; i < n_strings; i++)
788 : : {
789 : 728 : JsonNode *element = json_array_get_element (array, i);
790 : :
791 [ + - ]: 728 : if G_UNLIKELY (json_node_get_value_type (element) != G_TYPE_STRING)
792 : : return NULL;
793 : :
794 : 728 : 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 : 380 : valent_packet_validate (JsonNode *packet,
814 : : GError **error)
815 : : {
816 : 380 : JsonObject *root;
817 : 380 : JsonNode *node;
818 : :
819 [ + + ]: 380 : 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 [ + + ]: 378 : 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 : 376 : root = json_node_get_object (packet);
838 : :
839 [ + + + + ]: 376 : if G_UNLIKELY ((node = json_object_get_member (root, "type")) == NULL ||
840 : : json_node_get_value_type (node) != G_TYPE_STRING)
841 : : {
842 [ + + ]: 3 : g_set_error_literal (error,
843 : : VALENT_PACKET_ERROR,
844 : : node == NULL
845 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
846 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
847 : : "expected \"type\" field holding a string");
848 : 2 : return FALSE;
849 : : }
850 : :
851 [ + + + + ]: 374 : if G_UNLIKELY ((node = json_object_get_member (root, "body")) == NULL ||
852 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
853 : : {
854 [ + + ]: 3 : g_set_error_literal (error,
855 : : VALENT_PACKET_ERROR,
856 : : node == NULL
857 : : ? VALENT_PACKET_ERROR_MISSING_FIELD
858 : : : VALENT_PACKET_ERROR_INVALID_FIELD,
859 : : "expected \"body\" field holding an object");
860 : 2 : return FALSE;
861 : : }
862 : :
863 : : /* These two are optional, but have defined value types */
864 [ + + + + ]: 372 : if G_UNLIKELY ((node = json_object_get_member (root, "payloadSize")) != NULL &&
865 : : json_node_get_value_type (node) != G_TYPE_INT64)
866 : : {
867 : 1 : g_set_error_literal (error,
868 : : VALENT_PACKET_ERROR,
869 : : VALENT_PACKET_ERROR_INVALID_FIELD,
870 : : "expected \"payloadSize\" field to hold an integer");
871 : 1 : return FALSE;
872 : : }
873 : :
874 [ + + + + ]: 371 : if G_UNLIKELY ((node = json_object_get_member (root, "payloadTransferInfo")) != NULL &&
875 : : json_node_get_node_type (node) != JSON_NODE_OBJECT)
876 : : {
877 : 1 : g_set_error_literal (error,
878 : : VALENT_PACKET_ERROR,
879 : : VALENT_PACKET_ERROR_INVALID_FIELD,
880 : : "expected \"payloadTransferInfo\" field to hold an object");
881 : 1 : return FALSE;
882 : : }
883 : :
884 : : return TRUE;
885 : : }
886 : :
887 : : static void
888 : 21 : valent_packet_from_stream_task (GTask *task,
889 : : gpointer source_object,
890 : : gpointer task_data,
891 : : GCancellable *cancellable)
892 : : {
893 : 21 : GInputStream *stream = G_INPUT_STREAM (source_object);
894 : 21 : gssize max_len = (gssize)(intptr_t)task_data;
895 : 21 : g_autoptr (JsonParser) parser = NULL;
896 [ + - + + ]: 21 : g_autoptr (JsonNode) packet = NULL;
897 [ + - - + ]: 21 : g_autofree char *line = NULL;
898 : 21 : gssize count = 0;
899 : 21 : gssize size = 4096;
900 : 21 : GError *error = NULL;
901 : :
902 [ + - + - : 21 : g_assert (G_IS_INPUT_STREAM (stream));
+ - - + ]
903 : :
904 : : #ifndef __clang_analyzer__
905 [ + + ]: 21 : if (max_len < 0)
906 : 11 : max_len = G_MAXSSIZE;
907 : :
908 : 21 : line = g_malloc0 (size);
909 : :
910 : 8887 : while (TRUE)
911 : : {
912 : 8887 : gssize read = 0;
913 : :
914 [ + + ]: 8887 : if G_UNLIKELY (count == max_len)
915 : : {
916 : 1 : g_task_return_new_error (task,
917 : : G_IO_ERROR,
918 : : G_IO_ERROR_MESSAGE_TOO_LARGE,
919 : : "Packet too large");
920 : 1 : return;
921 : : }
922 : :
923 [ + + ]: 8886 : if G_UNLIKELY (count == size)
924 : : {
925 : 1 : size = MIN (size * 2, max_len);
926 : 1 : line = g_realloc (line, size);
927 : : }
928 : :
929 : 17772 : read = g_input_stream_read (stream,
930 : 8886 : line + count,
931 : : 1,
932 : : cancellable,
933 : : &error);
934 [ + + ]: 8886 : if (error != NULL)
935 : : {
936 : 1 : g_task_return_error (task, g_steal_pointer (&error));
937 : 1 : return;
938 : : }
939 : :
940 [ + + ]: 8885 : if (read > 0)
941 : 8883 : count += read;
942 [ - + ]: 2 : else if (read == 0)
943 : : break;
944 : :
945 [ + + + + ]: 8883 : if G_UNLIKELY (line[count - 1] == '\n')
946 : : break;
947 : : }
948 : :
949 : 19 : parser = json_parser_new_immutable ();
950 [ + + ]: 19 : if (!json_parser_load_from_data (parser, line, count, &error))
951 : : {
952 : 1 : g_task_return_error (task, g_steal_pointer (&error));
953 : 1 : return;
954 : : }
955 : :
956 : 18 : packet = json_parser_steal_root (parser);
957 [ + + ]: 18 : if (!valent_packet_validate (packet, &error))
958 : : {
959 : 2 : g_task_return_error (task, g_steal_pointer (&error));
960 : 2 : return;
961 : : }
962 : : #endif /* __clang_analyzer__ */
963 : :
964 : 16 : g_task_return_pointer (task,
965 : : g_steal_pointer (&packet),
966 : : (GDestroyNotify)json_node_unref);
967 : : }
968 : :
969 : : /**
970 : : * valent_packet_from_stream_async:
971 : : * @stream: a `GInputStream`
972 : : * @max_len: the maximum number bytes to read, or `-1` for no limit
973 : : * @cancellable: (nullable): a `GCancellable`
974 : : * @callback: (scope async): a `GAsyncReadyCallback`
975 : : * @user_data: user supplied data
976 : : *
977 : : * Read a KDE Connect packet from an input stream.
978 : : *
979 : : * If reading fails or the packet does not conform to the minimum structure of
980 : : * a KDE Connect packet, %NULL will be returned with @error set.
981 : : *
982 : : * If @max_len is greater than `-1`, then at most @max_len bytes will be read.
983 : : * If @max_len bytes are read without encountering a line-feed character, %NULL
984 : : * will be returned with @error set to %G_IO_ERROR_MESSAGE_TOO_LARGE.
985 : : *
986 : : * Call [func@Valent.packet_from_stream_finish] to get the result.
987 : : *
988 : : * Since: 1.0
989 : : */
990 : : void
991 : 21 : valent_packet_from_stream_async (GInputStream *stream,
992 : : gssize max_len,
993 : : GCancellable *cancellable,
994 : : GAsyncReadyCallback callback,
995 : : gpointer user_data)
996 : : {
997 : 42 : g_autoptr (GTask) task = NULL;
998 : 21 : gssize _max_len = max_len;
999 : :
1000 [ + - + - : 21 : g_return_if_fail (G_IS_INPUT_STREAM (stream));
+ - - + ]
1001 [ + + + - : 21 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
1002 : :
1003 : 21 : task = g_task_new (stream, cancellable, callback, user_data);
1004 [ + - ]: 21 : g_task_set_source_tag (task, valent_packet_from_stream_async);
1005 : 21 : g_task_set_task_data (task, (void *)(intptr_t)_max_len, NULL);
1006 [ + - ]: 21 : g_task_run_in_thread (task, valent_packet_from_stream_task);
1007 : : }
1008 : :
1009 : : /**
1010 : : * valent_packet_from_stream_finish:
1011 : : * @stream: a `GInputStream`
1012 : : * @result: a `GAsyncResult`
1013 : : * @error: (nullable): a `GError`
1014 : : *
1015 : : * Finish an operation started by [func@Valent.packet_from_stream].
1016 : : *
1017 : : * Returns: (transfer full): a KDE Connect packet, or %NULL with @error set
1018 : : *
1019 : : * Since: 1.0
1020 : : */
1021 : : JsonNode *
1022 : 21 : valent_packet_from_stream_finish (GInputStream *stream,
1023 : : GAsyncResult *result,
1024 : : GError **error)
1025 : : {
1026 [ + - + - : 21 : g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+ - - + ]
1027 [ + - ]: 21 : g_return_val_if_fail (g_task_is_valid (result, stream), NULL);
1028 [ + - + - ]: 21 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1029 : :
1030 : 21 : return g_task_propagate_pointer (G_TASK (result), error);
1031 : : }
1032 : :
1033 : : static void
1034 : 15 : valent_packet_from_stream_cb (GObject *object,
1035 : : GAsyncResult *result,
1036 : : gpointer user_data)
1037 : : {
1038 : 15 : GInputStream *stream = G_INPUT_STREAM (object);
1039 : 15 : GTask *task = G_TASK (user_data);
1040 : 15 : JsonNode *packet;
1041 : 15 : GError *error = NULL;
1042 : :
1043 : 15 : packet = valent_packet_from_stream_finish (stream, result, &error);
1044 [ + + ]: 15 : if (packet != NULL)
1045 : : {
1046 : 10 : g_task_return_pointer (task,
1047 : : g_steal_pointer (&packet),
1048 : : (GDestroyNotify)json_node_unref);
1049 : : }
1050 : : else
1051 : : {
1052 : 5 : g_task_return_error (task, g_steal_pointer (&error));
1053 : : }
1054 : 15 : }
1055 : :
1056 : : /**
1057 : : * valent_packet_from_stream:
1058 : : * @stream: a `GInputStream`
1059 : : * @max_len: the maximum number bytes to read, or `-1` for no limit
1060 : : * @cancellable: (nullable): a `GCancellable`
1061 : : * @error: (nullable): a `GError`
1062 : : *
1063 : : * Read a KDE Connect packet from an input stream.
1064 : : *
1065 : : * This is a synchronous wrapper around [func@Valent.packet_from_stream_async]
1066 : : * that iterates the thread-default main context, as returned by
1067 : : * [func@GLib.MainContext.ref_thread_default].
1068 : : *
1069 : : * Returns: (transfer full): a KDE Connect packet, or %NULL with @error set.
1070 : : *
1071 : : * Since: 1.0
1072 : : */
1073 : : JsonNode *
1074 : 15 : valent_packet_from_stream (GInputStream *stream,
1075 : : gssize max_len,
1076 : : GCancellable *cancellable,
1077 : : GError **error)
1078 : : {
1079 : 30 : g_autoptr (GMainContext) context = NULL;
1080 [ + - ]: 15 : g_autoptr (GTask) task = NULL;
1081 : :
1082 [ + - + - : 15 : g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
+ - - + ]
1083 [ + + + - : 15 : g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- + - - ]
1084 [ + - + - ]: 15 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1085 : :
1086 : 15 : task = g_task_new (NULL, NULL, NULL, NULL);
1087 [ + - ]: 15 : g_task_set_source_tag (task, valent_packet_from_stream);
1088 : :
1089 : 15 : valent_packet_from_stream_async (stream,
1090 : : max_len,
1091 : : cancellable,
1092 : : (GAsyncReadyCallback)valent_packet_from_stream_cb,
1093 : : task);
1094 : :
1095 : 15 : context = g_main_context_ref_thread_default ();
1096 [ + + ]: 5968 : while (!g_task_get_completed (task))
1097 : 5953 : g_main_context_iteration (context, TRUE);
1098 : :
1099 : 15 : return g_task_propagate_pointer (task, error);
1100 : : }
1101 : :
1102 : : static void
1103 : 6 : g_output_stream_write_all_cb (GOutputStream *stream,
1104 : : GAsyncResult *result,
1105 : : gpointer user_data)
1106 : : {
1107 : 12 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
1108 : 6 : size_t n_written;
1109 : 6 : GError *error = NULL;
1110 : :
1111 [ - + ]: 6 : if (!g_output_stream_write_all_finish (stream, result, &n_written, &error))
1112 : : {
1113 : 0 : g_task_return_error (task, g_steal_pointer (&error));
1114 [ # # ]: 0 : return;
1115 : : }
1116 : :
1117 [ + - ]: 6 : g_task_return_boolean (task, TRUE);
1118 : : }
1119 : :
1120 : : /**
1121 : : * valent_packet_to_stream_async:
1122 : : * @stream: a `GOutputStream`
1123 : : * @packet: a KDE Connect packet
1124 : : * @cancellable: (nullable): a `GCancellable`
1125 : : * @callback: (scope async): a `GAsyncReadyCallback`
1126 : : * @user_data: user supplied data
1127 : : *
1128 : : * A convenience function for writing a KDE Connect packet to an output stream.
1129 : : *
1130 : : * Call [func@Valent.packet_to_stream_finish] to get the result.
1131 : : *
1132 : : * Since: 1.0
1133 : : */
1134 : : void
1135 : 6 : valent_packet_to_stream_async (GOutputStream *stream,
1136 : : JsonNode *packet,
1137 : : GCancellable *cancellable,
1138 : : GAsyncReadyCallback callback,
1139 : : gpointer user_data)
1140 : : {
1141 : 6 : g_autoptr (GTask) task = NULL;
1142 [ + - ]: 6 : g_autoptr (JsonGenerator) generator = NULL;
1143 : 6 : JsonObject *root;
1144 [ + - ]: 6 : g_autofree char *packet_str = NULL;
1145 : 6 : size_t packet_len;
1146 : 6 : GError *error = NULL;
1147 : :
1148 [ + - + - : 6 : g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+ - - + ]
1149 [ + - ]: 6 : g_return_if_fail (packet != NULL);
1150 [ - + - - : 6 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
1151 : :
1152 [ - + ]: 6 : if (!valent_packet_validate (packet, &error))
1153 : : {
1154 : 0 : g_task_report_error (stream, callback, user_data,
1155 : : valent_packet_to_stream_async,
1156 : 0 : g_steal_pointer (&error));
1157 : 0 : return;
1158 : : }
1159 : :
1160 : : /* Timestamp the packet (UNIX Epoch ms)
1161 : : */
1162 : 6 : root = json_node_get_object (packet);
1163 : 6 : json_object_set_int_member (root, "id", valent_timestamp_ms ());
1164 : :
1165 : : /* Serialize the packet and replace the trailing NULL with an LF
1166 : : */
1167 : 6 : generator = json_generator_new ();
1168 : 6 : json_generator_set_root (generator, packet);
1169 : 6 : packet_str = json_generator_to_data (generator, &packet_len);
1170 : 6 : packet_str[packet_len++] = '\n';
1171 : :
1172 : 6 : task = g_task_new (stream, cancellable, callback, user_data);
1173 [ + - ]: 6 : g_task_set_source_tag (task, valent_packet_to_stream_async);
1174 : 6 : g_output_stream_write_all_async (stream,
1175 : : packet_str,
1176 : : packet_len,
1177 : : G_PRIORITY_DEFAULT,
1178 : : cancellable,
1179 : : (GAsyncReadyCallback)g_output_stream_write_all_cb,
1180 : : g_object_ref (task));
1181 : : }
1182 : :
1183 : : /**
1184 : : * valent_packet_to_stream_finish:
1185 : : * @stream: a `GInputStream`
1186 : : * @result: a `GAsyncResult`
1187 : : * @error: (nullable): a `GError`
1188 : : *
1189 : : * Finish an operation started by [func@Valent.packet_to_stream_async].
1190 : : *
1191 : : * Returns: %TRUE, or %FALSE with @error set
1192 : : *
1193 : : * Since: 1.0
1194 : : */
1195 : : gboolean
1196 : 6 : valent_packet_to_stream_finish (GOutputStream *stream,
1197 : : GAsyncResult *result,
1198 : : GError **error)
1199 : : {
1200 [ + - + - : 6 : g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
+ - - + ]
1201 [ + - ]: 6 : g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
1202 [ + - + - ]: 6 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1203 : :
1204 : 6 : return g_task_propagate_boolean (G_TASK (result), error);
1205 : : }
1206 : :
1207 : : /**
1208 : : * valent_packet_to_stream:
1209 : : * @stream: a `GOutputStream`
1210 : : * @packet: a KDE Connect packet
1211 : : * @cancellable: (nullable): a `GCancellable`
1212 : : * @error: (nullable): a `GError`
1213 : : *
1214 : : * Write a KDE Connect packet to an output stream.
1215 : : *
1216 : : * Returns: %TRUE if successful, or %FALSE with @error set
1217 : : *
1218 : : * Since: 1.0
1219 : : */
1220 : : gboolean
1221 : 10 : valent_packet_to_stream (GOutputStream *stream,
1222 : : JsonNode *packet,
1223 : : GCancellable *cancellable,
1224 : : GError **error)
1225 : : {
1226 : 20 : g_autoptr (JsonGenerator) generator = NULL;
1227 : 10 : JsonObject *root;
1228 [ + - ]: 10 : g_autofree char *packet_str = NULL;
1229 : 10 : size_t packet_len;
1230 : :
1231 [ + - + - : 10 : g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
+ - - + ]
1232 [ + - ]: 10 : g_return_val_if_fail (packet != NULL, FALSE);
1233 [ + + + - : 10 : g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
- + - - ]
1234 [ + - + - ]: 10 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1235 : :
1236 [ - + ]: 10 : if (!valent_packet_validate (packet, error))
1237 : : return FALSE;
1238 : :
1239 : : /* Timestamp the packet (UNIX Epoch ms)
1240 : : */
1241 : 10 : root = json_node_get_object (packet);
1242 : 10 : json_object_set_int_member (root, "id", valent_timestamp_ms ());
1243 : :
1244 : : /* Serialize the packet and replace the trailing NULL with an LF
1245 : : */
1246 : 10 : generator = json_generator_new ();
1247 : 10 : json_generator_set_root (generator, packet);
1248 : 10 : packet_str = json_generator_to_data (generator, &packet_len);
1249 : 10 : packet_str[packet_len++] = '\n';
1250 : :
1251 : 10 : return g_output_stream_write_all (stream,
1252 : : packet_str,
1253 : : packet_len,
1254 : : NULL,
1255 : : cancellable,
1256 : : error);
1257 : : }
1258 : :
1259 : : /**
1260 : : * valent_packet_serialize:
1261 : : * @packet: a complete KDE Connect packet
1262 : : * @length: (out) (nullable): a location for the length
1263 : : *
1264 : : * Convenience function that updates the timestamp of a packet before returning
1265 : : * a serialized string with newline ending, ready to be written to a stream.
1266 : : *
1267 : : * Returns: (transfer full) (nullable): the serialized packet.
1268 : : *
1269 : : * Since: 1.0
1270 : : */
1271 : : char *
1272 : 319 : valent_packet_serialize (JsonNode *packet,
1273 : : size_t *length)
1274 : : {
1275 : 319 : JsonObject *root;
1276 : 638 : g_autoptr (JsonGenerator) generator = NULL;
1277 : 319 : GString *packet_str = NULL;
1278 : :
1279 [ - + ]: 319 : g_return_val_if_fail (VALENT_IS_PACKET (packet), NULL);
1280 : :
1281 : : /* Timestamp the packet (UNIX Epoch ms)
1282 : : */
1283 : 319 : root = json_node_get_object (packet);
1284 : 319 : json_object_set_int_member (root, "id", valent_timestamp_ms ());
1285 : :
1286 : : /* Serialize the packet and append a newline character
1287 : : */
1288 : 319 : generator = json_generator_new ();
1289 : 319 : json_generator_set_root (generator, packet);
1290 : :
1291 : 319 : packet_str = g_string_new ("");
1292 : 319 : json_generator_to_gstring (generator, packet_str);
1293 [ + - ]: 319 : g_string_append_c (packet_str, '\n');
1294 : :
1295 [ + + ]: 319 : if (length != NULL)
1296 : 313 : *length = packet_str->len;
1297 : :
1298 : 319 : return g_string_free (packet_str, FALSE);
1299 : : }
1300 : :
1301 : : /**
1302 : : * valent_packet_deserialize:
1303 : : * @json: a complete KDE Connect packet
1304 : : * @error: (nullable): a `GError`
1305 : : *
1306 : : * Convenience function that deserializes a KDE Connect packet from a string
1307 : : * with basic validation. If @str is empty, this function will return %NULL.
1308 : : *
1309 : : * If parsing or validation fails, @error will be set and %NULL returned.
1310 : : *
1311 : : * Returns: (transfer full) (nullable): a KDE Connect packet
1312 : : *
1313 : : * Since: 1.0
1314 : : */
1315 : : JsonNode *
1316 : 309 : valent_packet_deserialize (const char *json,
1317 : : GError **error)
1318 : : {
1319 : 618 : g_autoptr (JsonParser) parser = NULL;
1320 [ + - ]: 309 : g_autoptr (JsonNode) packet = NULL;
1321 : :
1322 [ - + ]: 309 : g_return_val_if_fail (json != NULL, NULL);
1323 [ + - + - ]: 309 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1324 : :
1325 : 309 : parser = json_parser_new_immutable ();
1326 : :
1327 [ + - ]: 309 : if (!json_parser_load_from_data (parser, json, -1, error))
1328 : : return NULL;
1329 : :
1330 [ + - ]: 309 : if ((packet = json_parser_steal_root (parser)) == NULL)
1331 : : return NULL;
1332 : :
1333 [ - + ]: 309 : if (!valent_packet_validate (packet, error))
1334 : 0 : return NULL;
1335 : :
1336 : : return g_steal_pointer (&packet);
1337 : : }
1338 : :
|