]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/Private/cti-services.c
mDNSResponder-1310.40.42.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / Private / cti-services.c
1 /* cti_services.c
2 *
3 * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * This file contains the implementation of the SRP Advertising Proxy management
18 * API on MacOS, which is private API used to control and manage the advertising
19 * proxy.
20 */
21
22
23 #include <Block.h>
24 #include <os/log.h>
25 #include <netinet/in.h>
26 #include <net/if.h>
27 #include <netinet6/in6_var.h>
28 #include <netinet/icmp6.h>
29 #include <netinet6/nd6.h>
30 #include "xpc_clients.h"
31
32 #define THREAD_SERVICE_SEND_BOTH 1
33
34 #include "cti-services.h"
35
36 //*************************************************************************************************************
37 // Globals
38
39 static int client_serial_number;
40
41 typedef union {
42 cti_reply_t reply;
43 cti_tunnel_reply_t tunnel_reply;
44 cti_service_reply_t service_reply;
45 cti_prefix_reply_t prefix_reply;
46 cti_state_reply_t state_reply;
47 cti_partition_id_reply_t partition_id_reply;
48 cti_network_node_type_reply_t network_node_type_reply;
49 } cti_callback_t;
50
51 typedef void
52 (*cti_internal_callback_t)(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status);
53
54 struct _cti_connection_t
55 {
56 int ref_count;
57
58 // xpc_connection between client and daemon
59 xpc_connection_t NULLABLE connection;
60
61 // Callback function ptr for Client
62 cti_callback_t callback;
63
64 // Before we can send commands, we have to check in, so when starting up, we stash the initial command
65 // here until we get an acknowledgment for the checkin.
66 xpc_object_t *first_command;
67
68 // For commands that fetch properties and also track properties, this will contain the name of the property
69 // for which events are requested.
70 const char *property_name;
71
72 cti_internal_callback_t NONNULL internal_callback;
73
74 // Queue specified by client for scheduling its Callback
75 dispatch_queue_t NONNULL client_queue;
76
77 // Client context
78 void *NULLABLE context;
79
80 // Printed when debugging the event handler
81 const char *NONNULL command_name;
82
83 // True if we've gotten a response to the check-in message.
84 bool checked_in;
85 };
86
87 //*************************************************************************************************************
88 // Utility Functions
89
90 static void
91 cti_connection_finalize(cti_connection_t ref)
92 {
93 free(ref);
94 }
95
96 #define cti_connection_release(ref) cti_connection_release_(ref, __FILE__, __LINE__)
97 static void
98 cti_connection_release_(cti_connection_t ref, const char *file, int line)
99 {
100 ref->callback.reply = NULL;
101 if (ref->connection != NULL) {
102 xpc_connection_cancel(ref->connection);
103 }
104 RELEASE(ref, cti_connection_finalize);
105 }
106
107 static void
108 cti_xpc_connection_finalize(void *context)
109 {
110 cti_connection_release(context);
111 }
112
113 static char *
114 cti_xpc_copy_description(xpc_object_t object)
115 {
116 xpc_type_t type = xpc_get_type(object);
117 if (type == XPC_TYPE_UINT64) {
118 uint64_t num = xpc_uint64_get_value(object);
119 char buf[23];
120 snprintf(buf, sizeof buf, "%llu", num);
121 return strdup(buf);
122 } else if (type == XPC_TYPE_INT64) {
123 int64_t num = xpc_int64_get_value(object);
124 char buf[23];
125 snprintf(buf, sizeof buf, "%lld", num);
126 return strdup(buf);
127 } else if (type == XPC_TYPE_STRING) {
128 const char *str = xpc_string_get_string_ptr(object);
129 size_t len = xpc_string_get_length(object);
130 char *ret = malloc(len + 3);
131 if (ret != NULL) {
132 *ret = '"';
133 strlcpy(ret + 1, str, len + 1);
134 ret[len + 1] = '"';
135 ret[len + 2] = 0;
136 return ret;
137 }
138 } else if (type == XPC_TYPE_DATA) {
139 const uint8_t *data = xpc_data_get_bytes_ptr(object);
140 size_t i, len = xpc_data_get_length(object);
141 char *ret = malloc(len * 2 + 3);
142 if (ret != NULL) {
143 ret[0] = '0';
144 ret[1] = 'x';
145 for (i = 0; i < len; i++) {
146 snprintf(ret + i * 2, 3, "%02x", data[i]);
147 }
148 return ret;
149 }
150 } else if (type == XPC_TYPE_BOOL) {
151 bool flag = xpc_bool_get_value(object);
152 if (flag) {
153 return strdup("true");
154 } else {
155 return strdup("false");
156 }
157 } else if (type == XPC_TYPE_ARRAY) {
158 size_t avail, vlen, len = 0, i, count = xpc_array_get_count(object);
159 char **values = malloc(count * sizeof(*values));
160 char *ret, *p_ret;
161 if (values == NULL) {
162 return NULL;
163 }
164 xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
165 values[index] = cti_xpc_copy_description(value);
166 return true;
167 });
168 for (i = 0; i < count; i++) {
169 if (values[i] == NULL) {
170 len += 6;
171 } else {
172 len += strlen(values[i]) + 2;
173 }
174 }
175 ret = malloc(len + 3);
176 p_ret = ret;
177 avail = len + 1;
178 *p_ret++ = '[';
179 --avail;
180 for (i = 0; i < count; i++) {
181 if (p_ret != NULL) {
182 snprintf(p_ret, avail, "%s%s%s", i == 0 ? "" : " ", values[i] != NULL ? values[i] : "NULL", (i + 1 == count) ? "" : ",");
183 vlen = strlen(p_ret);
184 p_ret += vlen;
185 avail -= vlen;
186 }
187 if (values[i] != NULL) {
188 free(values[i]);
189 }
190 }
191 *p_ret++ = ']';
192 *p_ret++ = 0;
193 free(values);
194 return ret;
195 }
196 return xpc_copy_description(object);
197 }
198
199 static void
200 cti_log_object(const char *context, const char *command, const char *preamble, const char *divide, xpc_object_t *object, char *indent)
201 {
202 xpc_type_t type = xpc_get_type(object);
203 static char no_indent[] = "";
204 if (indent == NULL) {
205 indent = no_indent;
206 }
207 char *new_indent;
208 size_t depth;
209 char *desc;
210 char *compound_begin = "";
211 char *compound_end = "";
212
213 if (type == XPC_TYPE_DICTIONARY || type == XPC_TYPE_ARRAY) {
214 bool compact = true;
215 bool *p_compact = &compact;
216 if (type == XPC_TYPE_DICTIONARY) {
217 compound_begin = "{";
218 compound_end = "}";
219 xpc_dictionary_apply(object, ^bool (const char *__unused key, xpc_object_t value) {
220 xpc_type_t sub_type = xpc_get_type(value);
221 if (sub_type == XPC_TYPE_DICTIONARY) {
222 *p_compact = false;
223 } else if (sub_type == XPC_TYPE_ARRAY) {
224 xpc_array_apply(value, ^bool (size_t __unused index, xpc_object_t sub_value) {
225 xpc_type_t sub_sub_type = xpc_get_type(sub_value);
226 if (sub_sub_type == XPC_TYPE_DICTIONARY || sub_sub_type == XPC_TYPE_ARRAY) {
227 *p_compact = false;
228 }
229 return true;
230 });
231 }
232 return true;
233 });
234 } else {
235 compound_begin = "[";
236 compound_end = "]";
237 xpc_array_apply(object, ^bool (size_t __unused index, xpc_object_t value) {
238 xpc_type_t sub_type = xpc_get_type(value);
239 if (sub_type == XPC_TYPE_DICTIONARY || sub_type == XPC_TYPE_ARRAY) {
240 *p_compact = false;
241 }
242 return true;
243 });
244 }
245 if (compact) {
246 size_t i, count;
247 const char **keys = NULL;
248 char **values;
249 char linebuf[160], *p_space;
250 size_t space_avail = sizeof(linebuf);
251 bool first = true;
252
253 if (type == XPC_TYPE_DICTIONARY) {
254 count = xpc_dictionary_get_count(object);
255 } else {
256 count = xpc_array_get_count(object);
257 }
258
259 values = malloc(count * sizeof(*values));
260 if (values == NULL) {
261 INFO("cti_log_object: no memory");
262 return;
263 }
264 if (type == XPC_TYPE_DICTIONARY) {
265 int index = 0, *p_index = &index;
266 keys = malloc(count * sizeof(*keys));
267 if (keys == NULL) {
268 free(values);
269 INFO("cti_log_object: no memory");
270 }
271 xpc_dictionary_apply(object, ^bool (const char *key, xpc_object_t value) {
272 values[*p_index] = cti_xpc_copy_description(value);
273 keys[*p_index] = key;
274 (*p_index)++;
275 return true;
276 });
277 } else {
278 xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
279 values[index] = cti_xpc_copy_description(value);
280 return true;
281 });
282 }
283 p_space = linebuf;
284 for (i = 0; i < count; i++) {
285 char *str = values[i];
286 size_t len;
287 char *eol = "";
288 bool emitted = false;
289 if (str == NULL) {
290 str = "NULL";
291 len = 6;
292 } else {
293 len = strlen(str) + 2;
294 }
295 if (type == XPC_TYPE_DICTIONARY) {
296 #ifdef __clang_analyzer__
297 len = 2;
298 #else
299 len += strlen(keys[i]) + 2; // "key: "
300 #endif
301 }
302 if (len + 1 > space_avail) {
303 if (i + 1 == count) {
304 eol = compound_end;
305 }
306 if (space_avail != sizeof(linebuf)) {
307 if (first) {
308 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
309 context, command, indent, preamble, divide, compound_begin, linebuf, eol);
310 first = false;
311 } else {
312 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP PUB_S_SRP,
313 context, command, indent, preamble, divide, linebuf, eol);
314 }
315 space_avail = sizeof linebuf;
316 p_space = linebuf;
317 }
318 if (len + 1 > space_avail) {
319 if (type == XPC_TYPE_DICTIONARY) {
320 #ifndef __clang_analyzer__
321 if (first) {
322 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP,
323 context, command, indent, preamble, divide, compound_begin, keys[i], str, eol);
324 first = false;
325 } else {
326 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP,
327 context, command, indent, preamble, divide, keys[i], str, eol);
328 }
329 #endif
330 } else {
331 if (first) {
332 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
333 context, command, indent, preamble, divide, compound_begin, str, eol);
334 first = false;
335 } else {
336 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP PUB_S_SRP,
337 context, command, indent, preamble, divide, str, eol);
338 }
339 }
340 emitted = true;
341 }
342 }
343 if (!emitted) {
344 if (type == XPC_TYPE_DICTIONARY) {
345 #ifndef __clang_analyzer__
346 snprintf(p_space, space_avail, "%s%s: %s%s", i == 0 ? "" : " ", keys[i], str, i + 1 == count ? "" : ",");
347 #endif
348 } else {
349 snprintf(p_space, space_avail, "%s%s%s", i == 0 ? "" : " ", str, i + 1 == count ? "" : ",");
350 }
351 len = strlen(p_space);
352 p_space += len;
353 space_avail -= len;
354 }
355 if (values[i] != NULL) {
356 free(values[i]);
357 values[i] = NULL;
358 }
359 }
360 if (linebuf != p_space) {
361 if (first) {
362 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
363 context, command, indent, preamble, divide, compound_begin, linebuf, compound_end);
364 } else {
365 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " + " PUB_S_SRP PUB_S_SRP,
366 context, command, indent, preamble, divide, linebuf, compound_end);
367 }
368 }
369 free(values);
370 if (keys != NULL) {
371 free(keys);
372 }
373 } else {
374 depth = strlen(indent);
375 new_indent = malloc(depth + 3);
376 if (new_indent == NULL) {
377 new_indent = indent;
378 } else {
379 memset(new_indent, ' ', depth + 2);
380 new_indent[depth + 2] = 0;
381 }
382 if (type == XPC_TYPE_DICTIONARY) {
383 xpc_dictionary_apply(object, ^bool (const char *key, xpc_object_t value) {
384 cti_log_object(context, command, key, ": ", value, new_indent);
385 return true;
386 });
387 } else {
388 xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
389 char numbuf[23];
390 snprintf(numbuf, sizeof(numbuf), "%zd", index);
391 cti_log_object(context, command, numbuf, ": ", value, new_indent);
392 return true;
393 });
394 }
395 if (new_indent != indent) {
396 free(new_indent);
397 }
398 }
399 } else {
400 desc = cti_xpc_copy_description(object);
401 INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP,
402 context, command, indent, preamble, divide, desc);
403 free(desc);
404 }
405 }
406
407 static void
408 cti_event_handler(xpc_object_t event, cti_connection_t conn_ref)
409 {
410 if (event == XPC_ERROR_CONNECTION_INVALID) {
411 INFO("cti_event_handler (" PUB_S_SRP "): cleanup", conn_ref->command_name);
412 if (conn_ref->callback.reply != NULL) {
413 conn_ref->internal_callback(conn_ref, event, kCTIStatus_Disconnected);
414 } else {
415 INFO("No callback");
416 }
417 if (conn_ref->connection != NULL) {
418 xpc_release(conn_ref->connection);
419 conn_ref->connection = NULL;
420 }
421 } else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
422 cti_log_object("cti_event_handler", conn_ref->command_name, "", "", event, "");
423 if (!conn_ref->checked_in) {
424 xpc_object_t command_result = xpc_dictionary_get_value(event, "commandResult");
425 int status = 0;
426 if (command_result != NULL) {
427 status = (int)xpc_int64_get_value(command_result);
428 if (status == 0) {
429 xpc_object_t command_data = xpc_dictionary_get_value(event, "commandData");
430 if (command_data == NULL) {
431 status = 0;
432 } else {
433 xpc_object_t ret_value = xpc_dictionary_get_value(command_data, "ret");
434 if (ret_value == NULL) {
435 status = 0;
436 } else {
437 status = (int)xpc_int64_get_value(ret_value);
438 }
439 }
440 }
441 }
442
443 if (status != 0) {
444 conn_ref->internal_callback(conn_ref, event, kCTIStatus_UnknownError);
445 xpc_connection_cancel(conn_ref->connection);
446 } else if (conn_ref->property_name != NULL) {
447 // We're meant to both get the property and subscribe to events on it.
448 xpc_object_t *dict = xpc_dictionary_create(NULL, NULL, 0);
449 if (dict == NULL) {
450 ERROR("cti_event_handler(" PUB_S_SRP "): no memory.", conn_ref->command_name);
451 xpc_connection_cancel(conn_ref->connection);
452 } else {
453 xpc_object_t *array = xpc_array_create(NULL, 0);
454 if (array == NULL) {
455 ERROR("cti_event_handler(" PUB_S_SRP "): no memory.", conn_ref->command_name);
456 xpc_connection_cancel(conn_ref->connection);
457 } else {
458 xpc_dictionary_set_string(dict, "command", "eventsOn");
459 xpc_dictionary_set_string(dict, "clientName", "wpanctl");
460 xpc_dictionary_set_value(dict, "eventList", array);
461 xpc_array_set_string(array, XPC_ARRAY_APPEND, conn_ref->property_name);
462 conn_ref->property_name = NULL;
463 cti_log_object("cti_event_handler/events on", conn_ref->command_name, "", "", dict, "");
464 xpc_connection_send_message_with_reply(conn_ref->connection, dict, conn_ref->client_queue,
465 ^(xpc_object_t in_event) {
466 cti_event_handler(in_event, conn_ref);
467 });
468 xpc_release(array);
469 }
470 xpc_release(dict);
471 }
472 } else {
473 xpc_object_t *message = conn_ref->first_command;
474 conn_ref->first_command = NULL;
475 cti_log_object("cti_event_handler/command is", conn_ref->command_name, "", "", message, "");
476 conn_ref->checked_in = true;
477
478 xpc_connection_send_message_with_reply(conn_ref->connection, message, conn_ref->client_queue,
479 ^(xpc_object_t in_event) {
480 cti_event_handler(in_event, conn_ref);
481 });
482 xpc_release(message);
483 }
484 } else {
485 conn_ref->internal_callback(conn_ref, event, kCTIStatus_NoError);
486 }
487 } else {
488 cti_log_object("cti_event_handler/other", conn_ref->command_name, "", "", event, "");
489 ERROR("cti_event_handler: Unexpected Connection Error [" PUB_S_SRP "]",
490 xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
491 conn_ref->internal_callback(conn_ref, NULL, kCTIStatus_DaemonNotRunning);
492 if (event != XPC_ERROR_CONNECTION_INTERRUPTED) {
493 xpc_connection_cancel(conn_ref->connection);
494 }
495 }
496 }
497
498 // Creates a new cti_ Connection Reference(cti_connection_t)
499 static cti_status_t
500 init_connection(cti_connection_t *ref, const char *servname, xpc_object_t *dict, const char *command_name,
501 const char *property_name, void *context, cti_callback_t app_callback,
502 cti_internal_callback_t internal_callback, dispatch_queue_t client_queue, const char *file, int line)
503 {
504 // Use an cti_connection_t on the stack to be captured in the blocks below, rather than
505 // capturing the cti_connection_t* owned by the client
506 cti_connection_t conn_ref = calloc(1, sizeof(struct _cti_connection_t));
507 if (conn_ref == NULL) {
508 ERROR("dns_services: init_connection() No memory to allocate!");
509 return kCTIStatus_NoMemory;
510 }
511
512 // Initialize the cti_connection_t
513 dispatch_retain(client_queue);
514 conn_ref->command_name = command_name;
515 conn_ref->property_name = property_name;
516 conn_ref->context = context;
517 conn_ref->client_queue = client_queue;
518 conn_ref->callback = app_callback;
519 conn_ref->internal_callback = internal_callback;
520 conn_ref->connection = xpc_connection_create_mach_service(servname, conn_ref->client_queue,
521 XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
522 conn_ref->first_command = dict;
523 xpc_retain(dict);
524
525 cti_log_object("init_connection/command", conn_ref->command_name, "", "", dict, "");
526
527 if (conn_ref->connection == NULL)
528 {
529 ERROR("dns_services: init_connection() conn_ref/lib_q is NULL");
530 if (conn_ref != NULL) {
531 free(conn_ref);
532 }
533 return kCTIStatus_NoMemory;
534 }
535
536 RETAIN_HERE(conn_ref); // For the event handler.
537 xpc_connection_set_event_handler(conn_ref->connection, ^(xpc_object_t event) { cti_event_handler(event, conn_ref); });
538 xpc_connection_set_finalizer_f(conn_ref->connection, cti_xpc_connection_finalize);
539 xpc_connection_set_context(conn_ref->connection, conn_ref);
540 xpc_connection_resume(conn_ref->connection);
541
542 char srp_name[] = "srp-mdns-proxyd";
543 char client_name[sizeof(srp_name) + 20];
544 snprintf(client_name, sizeof client_name, "%s-%d", srp_name, client_serial_number);
545 client_serial_number++;
546
547 xpc_object_t checkin_command = xpc_dictionary_create(NULL, NULL, 0);
548
549 xpc_dictionary_set_string(checkin_command, "command", "checkIn");
550 xpc_dictionary_set_string(checkin_command, "clientName", client_name);
551
552 cti_log_object("init_connection/checkin", conn_ref->command_name, "", "", checkin_command, "");
553 xpc_connection_send_message_with_reply(conn_ref->connection, checkin_command, conn_ref->client_queue,
554 ^(xpc_object_t event) { cti_event_handler(event, conn_ref); });
555
556 xpc_release(checkin_command);
557 if (ref) {
558 *ref = conn_ref;
559 }
560 // We always retain a reference for the caller, even if the caller doesn't actually hold the reference.
561 // Calls that do not result in repeated callbacks release this reference after calling the callback.
562 // Such calls do not return a reference to the caller, so there is no chance of a double release.
563 // Calls that result in repeated callbacks have to release the reference by calling cti_events_discontinue.
564 // If this isn't done, the reference will never be released.
565 RETAIN(conn_ref);
566
567 return kCTIStatus_NoError;
568 }
569
570 #define setup_for_command(ref, client_queue, command_name, property_name, dict, command, \
571 context, app_callback, internal_callback) \
572 setup_for_command_(ref, client_queue, command_name, property_name, dict, command, \
573 context, app_callback, internal_callback, __FILE__, __LINE__)
574 static cti_status_t
575 setup_for_command_(cti_connection_t *ref, dispatch_queue_t client_queue, const char *command_name,
576 const char *property_name, xpc_object_t dict, const char *command, void *context,
577 cti_callback_t app_callback, cti_internal_callback_t internal_callback, const char *file, int line)
578 {
579 cti_status_t errx = kCTIStatus_NoError;
580
581 // Sanity Checks
582 if (app_callback.reply == NULL || internal_callback == NULL || client_queue == NULL)
583 {
584 ERROR(PUB_S_SRP ": NULL cti_connection_t OR Callback OR Client_Queue parameter", command_name);
585 return kCTIStatus_BadParam;
586 }
587
588 // Get conn_ref from init_connection()
589 xpc_dictionary_set_string(dict, "command", command);
590
591 errx = init_connection(ref, "com.apple.wpantund.xpc", dict, command_name, property_name,
592 context, app_callback, internal_callback, client_queue, file, line);
593 if (errx) // On error init_connection() leaves *conn_ref set to NULL
594 {
595 ERROR(PUB_S_SRP ": Since init_connection() returned %d error returning w/o sending msg", command_name, errx);
596 return errx;
597 }
598
599 return errx;
600 }
601
602 static void
603 cti_internal_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t __unused reply, cti_status_t status)
604 {
605 cti_reply_t callback;
606 INFO("cti_internal_reply_callback: conn_ref = %p", conn_ref);
607 callback = conn_ref->callback.reply;
608 if (callback != NULL) {
609 callback(conn_ref->context, status);
610 }
611 cti_connection_release(conn_ref);
612 }
613
614 cti_status_t
615 cti_add_service(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
616 uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length,
617 const uint8_t *NONNULL server_data, size_t server_data_length)
618 {
619 cti_status_t errx;
620 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
621
622 cti_callback_t app_callback;
623 app_callback.reply = callback;
624
625 xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
626 xpc_dictionary_set_data(dict, "server_data", server_data, server_data_length);
627 xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
628 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
629 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
630 xpc_dictionary_set_string(dict, "method", "ServiceAdd");
631 xpc_dictionary_set_bool(dict, "stable", true);
632
633 errx = setup_for_command(NULL, client_queue, "add_service", NULL, dict, "WpanctlCmd",
634 context, app_callback, cti_internal_reply_callback);
635 xpc_release(dict);
636
637 return errx;
638 }
639
640 cti_status_t
641 cti_remove_service(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
642 uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length)
643 {
644 cti_status_t errx;
645 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
646
647 cti_callback_t app_callback;
648 app_callback.reply = callback;
649
650 xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
651 xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
652 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
653 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
654 xpc_dictionary_set_string(dict, "method", "ServiceRemove");
655
656 errx = setup_for_command(NULL, client_queue, "remove_service", NULL, dict, "WpanctlCmd",
657 context, app_callback, cti_internal_reply_callback);
658 xpc_release(dict);
659
660 return errx;
661 }
662
663 static cti_status_t
664 cti_do_prefix(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
665 struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable, bool adding)
666 {
667 cti_status_t errx;
668 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
669
670 cti_callback_t app_callback;
671 app_callback.reply = callback;
672
673 if (dict == NULL) {
674 ERROR("cti_do_prefix: no memory for command dictionary.");
675 return kCTIStatus_NoMemory;
676 }
677 xpc_dictionary_set_bool(dict, "preferred", preferred);
678 if (adding) {
679 xpc_dictionary_set_uint64(dict, "preferredLifetime", ND6_INFINITE_LIFETIME);
680 xpc_dictionary_set_uint64(dict, "validLifetime", ND6_INFINITE_LIFETIME);
681 } else {
682 xpc_dictionary_set_uint64(dict, "preferredLifetime", 0);
683 xpc_dictionary_set_uint64(dict, "validLifetime", 0);
684 }
685 xpc_dictionary_set_int64(dict, "prefix_length", 16);
686 xpc_dictionary_set_bool(dict, "dhcp", false);
687 xpc_dictionary_set_data(dict, "prefix", prefix, sizeof(*prefix));
688 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
689 xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length);
690 xpc_dictionary_set_bool(dict, "slaac", slaac);
691 xpc_dictionary_set_bool(dict, "onMesh", on_mesh);
692 xpc_dictionary_set_bool(dict, "configure", false);
693 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
694 xpc_dictionary_set_string(dict, "method", "ConfigGateway");
695 xpc_dictionary_set_bool(dict, "stable", stable);
696 xpc_dictionary_set_bool(dict, "defaultRoute", adding);
697 xpc_dictionary_set_int64(dict, "priority", 0);
698
699 errx = setup_for_command(NULL, client_queue, "add_prefix", NULL, dict, "WpanctlCmd",
700 context, app_callback, cti_internal_reply_callback);
701 xpc_release(dict);
702
703 return errx;
704 }
705
706 cti_status_t
707 cti_add_prefix(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
708 struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable)
709 {
710 return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, on_mesh, preferred, slaac, stable, true);
711 }
712
713 cti_status_t
714 cti_remove_prefix(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
715 struct in6_addr *NONNULL prefix, int prefix_length)
716
717 {
718 return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, false, false, false, false, false);
719 }
720
721 static void
722 cti_internal_tunnel_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
723 {
724 cti_tunnel_reply_t callback = conn_ref->callback.tunnel_reply;
725 xpc_retain(reply);
726 cti_status_t status = status_in;
727 const char *tunnel_name = NULL;
728 uint64_t command_result = xpc_dictionary_get_int64(reply, "commandResult");
729 if (command_result != 0) {
730 ERROR("cti_internal_tunnel_reply_callback: nonzero result %llu", command_result);
731 status = kCTIStatus_UnknownError;
732 } else {
733 xpc_object_t result_dictionary = xpc_dictionary_get_dictionary(reply, "commandData");
734 if (status == kCTIStatus_NoError) {
735 if (result_dictionary != NULL) {
736 const char *property_name = xpc_dictionary_get_string(result_dictionary, "property_name");
737 if (property_name == NULL || strcmp(property_name, "Config:TUN:InterfaceName")) {
738 status = kCTIStatus_UnknownError;
739 } else {
740 tunnel_name = xpc_dictionary_get_string(result_dictionary, "value");
741 if (tunnel_name == NULL) {
742 status = kCTIStatus_UnknownError;
743 }
744 }
745 } else {
746 status = kCTIStatus_UnknownError;
747 }
748 }
749 }
750 if (callback != NULL) {
751 callback(conn_ref->context, tunnel_name, status);
752 }
753 xpc_release(reply);
754 conn_ref->callback.reply = NULL;
755 if (conn_ref->connection != NULL) {
756 xpc_connection_cancel(conn_ref->connection);
757 }
758 }
759
760 cti_status_t
761 cti_get_tunnel_name(void *NULLABLE context, cti_tunnel_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue)
762 {
763 cti_status_t errx;
764 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
765
766 cti_callback_t app_callback;
767 app_callback.tunnel_reply = callback;
768
769 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
770 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
771 xpc_dictionary_set_string(dict, "method", "PropGet");
772 xpc_dictionary_set_string(dict, "property_name", "Config:TUN:InterfaceName");
773
774 errx = setup_for_command(NULL, client_queue, "get_tunnel_name", NULL, dict, "WpanctlCmd",
775 context, app_callback, cti_internal_tunnel_reply_callback);
776 xpc_release(dict);
777
778 return errx;
779 }
780
781 static cti_status_t
782 cti_event_or_response_extract(xpc_object_t *reply, xpc_object_t *result_dictionary)
783 {
784 xpc_object_t *result = xpc_dictionary_get_dictionary(reply, "commandData");
785 if (result == NULL) {
786 result = xpc_dictionary_get_dictionary(reply, "eventData");
787 } else {
788 int command_status = (int)xpc_dictionary_get_int64(reply, "commandResult");
789 if (command_status != 0) {
790 INFO("cti_event_or_response_extract: nonzero status %d", command_status);
791 return kCTIStatus_UnknownError;
792 }
793 }
794 if (result != NULL) {
795 *result_dictionary = result;
796 return kCTIStatus_NoError;
797 }
798 INFO("cti_event_or_response_extract: null result");
799 return kCTIStatus_UnknownError;
800 }
801
802 static void
803 cti_internal_state_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
804 {
805 cti_state_reply_t callback = conn_ref->callback.state_reply;
806 cti_network_state_t state = kCTI_NCPState_Unknown;
807 cti_status_t status = status_in;
808 if (status == kCTIStatus_NoError) {
809 xpc_object_t result_dictionary = NULL;
810 status = cti_event_or_response_extract(reply, &result_dictionary);
811 if (status == kCTIStatus_NoError) {
812 const char *state_name = xpc_dictionary_get_string(result_dictionary, "value");
813 if (state_name == NULL) {
814 status = kCTIStatus_UnknownError;
815 } else if (!strcmp(state_name, "uninitialized")) {
816 state = kCTI_NCPState_Uninitialized;
817 } else if (!strcmp(state_name, "uninitialized:fault")) {
818 state = kCTI_NCPState_Fault;
819 } else if (!strcmp(state_name, "uninitialized:upgrading")) {
820 state = kCTI_NCPState_Upgrading;
821 } else if (!strcmp(state_name, "offline:deep-sleep")) {
822 state = kCTI_NCPState_DeepSleep;
823 } else if (!strcmp(state_name, "offline")) {
824 state = kCTI_NCPState_Offline;
825 } else if (!strcmp(state_name, "offline:commissioned")) {
826 state = kCTI_NCPState_Commissioned;
827 } else if (!strcmp(state_name, "associating")) {
828 state = kCTI_NCPState_Associating;
829 } else if (!strcmp(state_name, "associating:credentials-needed")) {
830 state = kCTI_NCPState_CredentialsNeeded;
831 } else if (!strcmp(state_name, "associated")) {
832 state = kCTI_NCPState_Associated;
833 } else if (!strcmp(state_name, "associated:no-parent")) {
834 state = kCTI_NCPState_Isolated;
835 } else if (!strcmp(state_name, "associated:netwake-asleep")) {
836 state = kCTI_NCPState_NetWake_Asleep;
837 } else if (!strcmp(state_name, "associated:netwake-waking")) {
838 state = kCTI_NCPState_NetWake_Waking;
839 }
840 }
841 }
842 if (callback != NULL) {
843 callback(conn_ref->context, state, status);
844 }
845 }
846
847 cti_status_t
848 cti_get_state(cti_connection_t *ref, void *NULLABLE context, cti_state_reply_t NONNULL callback,
849 dispatch_queue_t NONNULL client_queue)
850 {
851 cti_status_t errx;
852 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
853
854 cti_callback_t app_callback;
855 app_callback.state_reply = callback;
856
857 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
858 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
859 xpc_dictionary_set_string(dict, "method", "PropGet");
860 xpc_dictionary_set_string(dict, "property_name", "NCP:State");
861
862 errx = setup_for_command(ref, client_queue, "get_state", "NCP:State", dict, "WpanctlCmd",
863 context, app_callback, cti_internal_state_reply_callback);
864 xpc_release(dict);
865
866 return errx;
867 }
868
869 static void
870 cti_internal_partition_id_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
871 {
872 cti_partition_id_reply_t callback = conn_ref->callback.partition_id_reply;
873 int32_t partition_id = -1;
874 cti_status_t status = status_in;
875 if (status == kCTIStatus_NoError) {
876 xpc_object_t result_dictionary = NULL;
877 status = cti_event_or_response_extract(reply, &result_dictionary);
878 if (status == kCTIStatus_NoError) {
879 xpc_object_t value = xpc_dictionary_get_value(result_dictionary, "value");
880 if (value == NULL) {
881 ERROR("cti_internal_partition_id_callback: No partition ID returned.");
882 } else if (xpc_get_type(value) != XPC_TYPE_UINT64) {
883 char *value_string = xpc_copy_description(value);
884 ERROR("cti_internal_partition_id_callback: Partition ID is " PUB_S_SRP " instead if uint64_t.",
885 value_string);
886 free(value_string);
887 } else {
888 partition_id = (int32_t)xpc_dictionary_get_uint64(result_dictionary, "value");
889 }
890 }
891 }
892 if (callback != NULL) {
893 callback(conn_ref->context, partition_id, status);
894 }
895 }
896
897 cti_status_t
898 cti_get_partition_id(cti_connection_t *ref, void *NULLABLE context, cti_partition_id_reply_t NONNULL callback,
899 dispatch_queue_t NONNULL client_queue)
900 {
901 cti_status_t errx;
902 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
903
904 cti_callback_t app_callback;
905 app_callback.partition_id_reply = callback;
906
907 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
908 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
909 xpc_dictionary_set_string(dict, "method", "PropGet");
910 xpc_dictionary_set_string(dict, "property_name", "Network:PartitionId");
911
912 errx = setup_for_command(ref, client_queue, "get_partition_id", "Network:PartitionId", dict, "WpanctlCmd",
913 context, app_callback, cti_internal_partition_id_callback);
914 xpc_release(dict);
915
916 return errx;
917 }
918
919 static void
920 cti_internal_network_node_type_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
921 {
922 cti_network_node_type_reply_t callback = conn_ref->callback.network_node_type_reply;
923 cti_network_node_type_t network_node_type = kCTI_NetworkNodeType_Unknown;
924 cti_status_t status = status_in;
925 if (status == kCTIStatus_NoError) {
926 xpc_object_t result_dictionary = NULL;
927 status = cti_event_or_response_extract(reply, &result_dictionary);
928 if (status == kCTIStatus_NoError) {
929 xpc_object_t value = xpc_dictionary_get_value(result_dictionary, "value");
930 if (value == NULL) {
931 ERROR("cti_internal_network_node_type_callback: No node type returned.");
932 } else if (xpc_get_type(value) != XPC_TYPE_STRING) {
933 char *value_string = xpc_copy_description(value);
934 ERROR("cti_internal_network_node_type_callback: node type type is " PUB_S_SRP " instead of string.",
935 value_string);
936 free(value_string);
937 } else {
938 const char *node_type_name = xpc_dictionary_get_string(result_dictionary, "value");
939 if (!strcmp(node_type_name, "unknown")) {
940 network_node_type = kCTI_NetworkNodeType_Unknown;
941 } else if (!strcmp(node_type_name, "router")) {
942 network_node_type = kCTI_NetworkNodeType_Router;
943 } else if (!strcmp(node_type_name, "end-device")) {
944 network_node_type = kCTI_NetworkNodeType_EndDevice;
945 } else if (!strcmp(node_type_name, "sleepy-end-device")) {
946 network_node_type = kCTI_NetworkNodeType_SleepyEndDevice;
947 } else if (!strcmp(node_type_name, "nl-lurker")) {
948 network_node_type = kCTI_NetworkNodeType_NestLurker;
949 } else if (!strcmp(node_type_name, "commissioner")) {
950 network_node_type = kCTI_NetworkNodeType_Commissioner;
951 } else if (!strcmp(node_type_name, "leader")) {
952 network_node_type = kCTI_NetworkNodeType_Leader;
953 }
954 }
955 }
956 }
957 if (callback != NULL) {
958 callback(conn_ref->context, network_node_type, status);
959 }
960 }
961
962 cti_status_t
963 cti_get_network_node_type(cti_connection_t *ref, void *NULLABLE context, cti_network_node_type_reply_t NONNULL callback,
964 dispatch_queue_t NONNULL client_queue)
965 {
966 cti_status_t errx;
967 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
968
969 cti_callback_t app_callback;
970 app_callback.network_node_type_reply = callback;
971
972 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
973 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
974 xpc_dictionary_set_string(dict, "method", "PropGet");
975 xpc_dictionary_set_string(dict, "property_name", "Network:NodeType");
976
977 errx = setup_for_command(ref, client_queue, "get_network_node_type", "Network:NodeType", dict, "WpanctlCmd",
978 context, app_callback, cti_internal_network_node_type_callback);
979 xpc_release(dict);
980
981 return errx;
982 }
983
984 static void
985 cti_service_finalize(cti_service_t *service)
986 {
987 if (service->server != NULL) {
988 free(service->server);
989 }
990 free(service);
991 }
992
993 static void
994 cti_service_vec_finalize(cti_service_vec_t *services)
995 {
996 size_t i;
997
998 if (services->services != NULL) {
999 for (i = 0; i < services->num; i++) {
1000 if (services->services[i] != NULL) {
1001 RELEASE_HERE(services->services[i], cti_service_finalize);
1002 }
1003 }
1004 free(services->services);
1005 }
1006 free(services);
1007 }
1008
1009 cti_service_vec_t *
1010 cti_service_vec_create_(size_t num_services, const char *file, int line)
1011 {
1012 cti_service_vec_t *services = calloc(1, sizeof(*services));
1013 if (services != NULL) {
1014 if (num_services != 0) {
1015 services->services = calloc(num_services, sizeof(cti_service_t *));
1016 if (services->services == NULL) {
1017 free(services);
1018 return NULL;
1019 }
1020 }
1021 services->num = num_services;
1022 RETAIN(services);
1023 }
1024 return services;
1025 }
1026
1027 void
1028 cti_service_vec_release_(cti_service_vec_t *services, const char *file, int line)
1029 {
1030 RELEASE(services, cti_service_vec_finalize);
1031 }
1032
1033 cti_service_t *
1034 cti_service_create_(uint64_t enterprise_number, uint16_t service_type, uint16_t service_version,
1035 uint8_t *server, size_t server_length, int flags, const char *file, int line)
1036 {
1037 cti_service_t *service = calloc(1, sizeof(*service));
1038 if (service != NULL) {
1039 service->enterprise_number = enterprise_number;
1040 service->service_type = service_type;
1041 service->service_version = service_version;
1042 service->server = server;
1043 service->server_length = server_length;
1044 service->flags = flags;
1045 RETAIN(service);
1046 }
1047 return service;
1048 }
1049
1050 void
1051 cti_service_release_(cti_service_t *service, const char *file, int line)
1052 {
1053 RELEASE(service, cti_service_finalize);
1054 }
1055
1056 static uint8_t *
1057 cti_array_to_bytes(xpc_object_t array, size_t *length_ret, const char *log_name)
1058 {
1059 size_t length = xpc_array_get_count(array);
1060 size_t i;
1061 uint8_t *ret;
1062
1063 ret = malloc(length);
1064 if (ret == NULL) {
1065 ERROR(PUB_S_SRP ": no memory for return buffer", log_name);
1066 return NULL;
1067 }
1068
1069 for (i = 0; i < length; i++) {
1070 uint64_t v = xpc_array_get_uint64(array, i);
1071 ret[i] = v;
1072 }
1073 *length_ret = length;
1074 return ret;
1075 }
1076
1077 static cti_status_t
1078 cti_parse_services_array(cti_service_vec_t **services, xpc_object_t services_array)
1079 {
1080 size_t services_array_length = xpc_array_get_count(services_array);
1081 size_t i, j;
1082 cti_service_vec_t *service_vec;
1083 cti_service_t *service;
1084 cti_status_t status = kCTIStatus_NoError;
1085
1086 service_vec = cti_service_vec_create(services_array_length);
1087 if (service_vec == NULL) {
1088 return kCTIStatus_NoMemory;
1089 }
1090
1091 // Array of arrays
1092 for (i = 0; i < services_array_length; i++) {
1093 xpc_object_t service_array = xpc_array_get_value(services_array, i);
1094 int match_count = 0;
1095 bool matched[5] = { false, false, false, false, false};
1096 uint64_t enterprise_number = 0;
1097 uint8_t *server_data = NULL;
1098 size_t server_data_length = 0;
1099 uint8_t *service_data = NULL;
1100 size_t service_data_length = 0;
1101 int flags = 0;
1102
1103 if (service_array == NULL) {
1104 ERROR("Unable to get service array %zd", i);
1105 } else {
1106 size_t service_array_length = xpc_array_get_count(service_array);
1107 for (j = 0; j < service_array_length; j++) {
1108 xpc_object_t *array_sub_dict = xpc_array_get_value(service_array, j);
1109 if (array_sub_dict == NULL) {
1110 ERROR("can't get service_array %zd subdictionary %zd", i, j);
1111 goto service_array_element_failed;
1112 } else {
1113 const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
1114 if (key == NULL) {
1115 ERROR("Invalid services array %zd subdictionary %zd: no key", i, j);
1116 goto service_array_element_failed;
1117 } else if (!strcmp(key, "EnterpriseNumber")) {
1118 if (matched[0]) {
1119 ERROR("services array %zd: Enterprise number appears twice.", i);
1120 goto service_array_element_failed;
1121 }
1122 enterprise_number = xpc_dictionary_get_uint64(array_sub_dict, "value");
1123 matched[0] = true;
1124 } else if (!strcmp(key, "Origin")) {
1125 if (matched[1]) {
1126 ERROR("Services array %zd: Origin appears twice.", i);
1127 goto service_array_element_failed;
1128 }
1129 const char *origin_string = xpc_dictionary_get_string(array_sub_dict, "value");
1130 if (origin_string == NULL) {
1131 ERROR("Unable to get origin string from services array %zd", i);
1132 goto service_array_element_failed;
1133 } else if (!strcmp(origin_string, "user")) {
1134 // Not NCP
1135 } else if (!strcmp(origin_string, "ncp")) {
1136 flags |= kCTIFlag_NCP;
1137 } else {
1138 ERROR("unknown origin " PUB_S_SRP, origin_string);
1139 goto service_array_element_failed;
1140 }
1141 matched[1] = true;
1142 } else if (!strcmp(key, "ServerData")) {
1143 if (matched[2]) {
1144 ERROR("Services array %zd: Server data appears twice.", i);
1145 goto service_array_element_failed;
1146 }
1147 server_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
1148 &server_data_length, "Server data");
1149 if (server_data == NULL) {
1150 goto service_array_element_failed;
1151 }
1152 matched[2] = true;
1153 } else if (!strcmp(key, "ServiceData")) {
1154 if (matched[3]) {
1155 ERROR("Services array %zd: Service data appears twice.", i);
1156 goto service_array_element_failed;
1157 }
1158 service_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
1159 &service_data_length, "Service data");
1160 if (service_data == NULL) {
1161 goto service_array_element_failed;
1162 }
1163 matched[3] = true;
1164 } else if (!strcmp(key, "Stable")) {
1165 if (matched[4]) {
1166 ERROR("Services array %zd: Stable state appears twice.", i);
1167 goto service_array_element_failed;
1168 }
1169 if (xpc_dictionary_get_bool(array_sub_dict, "value")) {
1170 flags |= kCTIFlag_Stable;
1171 }
1172 matched[4] = true;
1173 } else {
1174 ERROR("Unknown key in service array %zd subdictionary %zd: " PUB_S_SRP, i, j, key);
1175 goto service_array_element_failed;
1176 }
1177 match_count++;
1178 }
1179 }
1180 if (match_count != 5) {
1181 ERROR("expecting %d sub-dictionaries to service array %zd, but got %d.",
1182 5, i, match_count);
1183 goto service_array_element_failed;
1184 }
1185 uint16_t service_type, service_version;
1186 if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
1187 if (service_data_length != 1) {
1188 INFO("Invalid service data: length = %zd", service_data_length);
1189 goto service_array_element_failed;
1190 }
1191 service_type = service_data[0];
1192 service_version = 1;
1193 } else {
1194 // We don't support any other enterprise numbers.
1195 service_type = service_version = 0;
1196 }
1197
1198 service = cti_service_create(enterprise_number, service_type, service_version,
1199 server_data, server_data_length, flags);
1200 if (service == NULL) {
1201 ERROR("Unable to store service %lld %d %d: out of memory.", enterprise_number,
1202 service_type, service_version);
1203 } else {
1204 server_data = NULL;
1205 service_vec->services[i] = service;
1206 }
1207 goto done_with_service_array;
1208 service_array_element_failed:
1209 if (status == kCTIStatus_NoError) {
1210 status = kCTIStatus_UnknownError;
1211 }
1212 done_with_service_array:
1213 if (server_data != NULL) {
1214 free(server_data);
1215 }
1216 if (service_data != NULL) {
1217 free(service_data);
1218 }
1219 }
1220 }
1221 if (status == kCTIStatus_NoError) {
1222 *services = service_vec;
1223 } else {
1224 if (service_vec != NULL) {
1225 RELEASE_HERE(service_vec, cti_service_vec_finalize);
1226 }
1227 }
1228 return status;
1229 }
1230
1231 static void
1232 cti_internal_service_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
1233 {
1234 cti_service_reply_t callback = conn_ref->callback.service_reply;
1235 cti_service_vec_t *vec = NULL;
1236 cti_status_t status = status_in;
1237 if (status == kCTIStatus_NoError) {
1238 xpc_object_t result_dictionary = NULL;
1239 status = cti_event_or_response_extract(reply, &result_dictionary);
1240 if (status == kCTIStatus_NoError) {
1241 xpc_object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
1242 if (value == NULL) {
1243 INFO("cti_internal_service_reply_callback: services array not present in Thread:Services event.");
1244 } else {
1245 status = cti_parse_services_array(&vec, value);
1246 }
1247 }
1248 }
1249 if (callback != NULL) {
1250 callback(conn_ref->context, vec, status);
1251 }
1252 if (vec != NULL) {
1253 RELEASE_HERE(vec, cti_service_vec_finalize);
1254 }
1255 }
1256
1257 cti_status_t
1258 cti_get_service_list(cti_connection_t *ref, void *NULLABLE context, cti_service_reply_t NONNULL callback,
1259 dispatch_queue_t NONNULL client_queue)
1260 {
1261 cti_status_t errx;
1262 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
1263
1264 cti_callback_t app_callback;
1265 app_callback.service_reply = callback;
1266
1267 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
1268 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
1269 xpc_dictionary_set_string(dict, "method", "PropGet");
1270 xpc_dictionary_set_string(dict, "property_name", "Thread:Services");
1271
1272 errx = setup_for_command(ref, client_queue, "get_service_list", "Thread:Services", dict, "WpanctlCmd",
1273 context, app_callback, cti_internal_service_reply_callback);
1274 xpc_release(dict);
1275
1276 return errx;
1277 }
1278
1279 static void
1280 cti_prefix_finalize(cti_prefix_t *prefix)
1281 {
1282 free(prefix);
1283 }
1284
1285 static void
1286 cti_prefix_vec_finalize(cti_prefix_vec_t *prefixes)
1287 {
1288 size_t i;
1289
1290 if (prefixes->prefixes != NULL) {
1291 for (i = 0; i < prefixes->num; i++) {
1292 if (prefixes->prefixes[i] != NULL) {
1293 RELEASE_HERE(prefixes->prefixes[i], cti_prefix_finalize);
1294 }
1295 }
1296 free(prefixes->prefixes);
1297 }
1298 free(prefixes);
1299 }
1300
1301 cti_prefix_vec_t *
1302 cti_prefix_vec_create_(size_t num_prefixes, const char *file, int line)
1303 {
1304 cti_prefix_vec_t *prefixes = calloc(1, sizeof(*prefixes));
1305 if (prefixes != NULL) {
1306 if (num_prefixes != 0) {
1307 prefixes->prefixes = calloc(num_prefixes, sizeof(cti_prefix_t *));
1308 if (prefixes->prefixes == NULL) {
1309 free(prefixes);
1310 return NULL;
1311 }
1312 }
1313 prefixes->num = num_prefixes;
1314 RETAIN(prefixes);
1315 }
1316 return prefixes;
1317 }
1318
1319 void
1320 cti_prefix_vec_release_(cti_prefix_vec_t *prefixes, const char *file, int line)
1321 {
1322 RELEASE(prefixes, cti_prefix_vec_finalize);
1323 }
1324
1325 cti_prefix_t *
1326 cti_prefix_create_(struct in6_addr *prefix, int prefix_length, int metric, int flags, const char *file, int line)
1327 {
1328 cti_prefix_t *prefix_ret = calloc(1, sizeof(*prefix_ret));
1329 if (prefix != NULL) {
1330 prefix_ret->prefix = *prefix;
1331 prefix_ret->prefix_length = prefix_length;
1332 prefix_ret->metric = metric;
1333 prefix_ret->flags = flags;
1334 RETAIN(prefix_ret);
1335 }
1336 return prefix_ret;
1337 }
1338
1339 void
1340 cti_prefix_release_(cti_prefix_t *prefix, const char *file, int line)
1341 {
1342 RELEASE(prefix, cti_prefix_finalize);
1343 }
1344
1345 static cti_status_t
1346 cti_parse_prefixes_array(cti_prefix_vec_t **vec_ret, xpc_object_t prefixes_array)
1347 {
1348 size_t prefixes_array_length = xpc_array_get_count(prefixes_array);
1349 size_t i, j;
1350 cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefixes_array_length);
1351 cti_status_t status = kCTIStatus_NoError;
1352
1353 if (prefixes == NULL) {
1354 INFO("cti_parse_prefixes_array: no memory.");
1355 status = kCTIStatus_NoMemory;
1356 } else {
1357 // Array of arrays
1358 for (i = 0; i < prefixes_array_length; i++) {
1359 xpc_object_t prefix_array = xpc_array_get_value(prefixes_array, i);
1360 int match_count = 0;
1361 bool matched[5] = { false, false};
1362 const char *destination = NULL;
1363 int metric = 0;
1364 struct in6_addr prefix_addr;
1365
1366 if (prefix_array == NULL) {
1367 ERROR("Unable to get prefix array %zu", i);
1368 } else {
1369 size_t prefix_array_length = xpc_array_get_count(prefix_array);
1370 for (j = 0; j < prefix_array_length; j++) {
1371 xpc_object_t *array_sub_dict = xpc_array_get_value(prefix_array, j);
1372 if (array_sub_dict == NULL) {
1373 ERROR("can't get prefix_array %zu subdictionary %zu", i, j);
1374 goto done_with_prefix_array;
1375 } else {
1376 const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
1377 if (key == NULL) {
1378 ERROR("Invalid prefixes array %zu subdictionary %zu: no key", i, j);
1379 goto done_with_prefix_array;
1380 }
1381 // Fix me: when <rdar://problem/59371674> is fixed, remove Addreess key test.
1382 else if (!strcmp(key, "Addreess") || !strcmp(key, "Address")) {
1383 if (matched[0]) {
1384 ERROR("prefixes array %zu: Address appears twice.", i);
1385 goto done_with_prefix_array;
1386 }
1387 destination = xpc_dictionary_get_string(array_sub_dict, "value");
1388 if (destination == NULL) {
1389 INFO("process_prefixes_array: null address");
1390 goto done_with_prefix_array;
1391 }
1392 matched[0] = true;
1393 } else if (!strcmp(key, "Metric")) {
1394 if (matched[1]) {
1395 ERROR("prefixes array %zu: Metric appears twice.", i);
1396 goto done_with_prefix_array;
1397 }
1398 metric = (int)xpc_dictionary_get_uint64(array_sub_dict, "value");
1399 } else {
1400 ERROR("Unknown key in prefix array %zu subdictionary %zu: " PUB_S_SRP, i, j, key);
1401 goto done_with_prefix_array;
1402 }
1403 match_count++;
1404 }
1405 }
1406 if (match_count != 2) {
1407 ERROR("expecting %d sub-dictionaries to prefix array %zu, but got %d.",
1408 2, i, match_count);
1409 goto done_with_prefix_array;
1410 }
1411
1412 // The prefix is in IPv6 address presentation form, so convert it to bits.
1413 char prefix_buffer[INET6_ADDRSTRLEN];
1414 const char *slash = strchr(destination, '/');
1415 size_t prefix_pres_len = slash - destination;
1416 if (prefix_pres_len >= INET6_ADDRSTRLEN - 1) {
1417 ERROR("prefixes array %zu: destination is longer than maximum IPv6 address string: " PUB_S_SRP,
1418 j, destination);
1419 goto done_with_prefix_array;
1420 }
1421 #ifndef __clang_analyzer__ // destination is never null at this point
1422 memcpy(prefix_buffer, destination, prefix_pres_len);
1423 #endif
1424 prefix_buffer[prefix_pres_len] = 0;
1425 inet_pton(AF_INET6, prefix_buffer, &prefix_addr);
1426
1427 // Also convert the prefix.
1428 char *endptr = NULL;
1429 int prefix_len = (int)strtol(slash + 1, &endptr, 10);
1430 if (endptr == slash + 1 || *endptr != 0 || prefix_len != 64) {
1431 INFO("bogus prefix length provided by thread: " PUB_S_SRP, destination);
1432 prefix_len = 64;
1433 }
1434
1435 cti_prefix_t *prefix = cti_prefix_create(&prefix_addr, prefix_len, metric, 0);
1436 if (prefix != NULL) {
1437 prefixes->prefixes[i] = prefix;
1438 }
1439 continue;
1440 done_with_prefix_array:
1441 status = kCTIStatus_UnknownError;
1442 }
1443 }
1444 }
1445 if (status == kCTIStatus_NoError) {
1446 *vec_ret = prefixes;
1447 } else {
1448 if (prefixes != NULL) {
1449 RELEASE_HERE(prefixes, cti_prefix_vec_finalize);
1450 }
1451 }
1452 return status;
1453 }
1454
1455 static void
1456 cti_internal_prefix_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
1457 {
1458 cti_prefix_reply_t callback = conn_ref->callback.prefix_reply;
1459 cti_status_t status = status_in;
1460 cti_prefix_vec_t *vec = NULL;
1461 xpc_object_t result_dictionary = NULL;
1462 if (status == kCTIStatus_NoError) {
1463 status = cti_event_or_response_extract(reply, &result_dictionary);
1464 if (status == kCTIStatus_NoError) {
1465 xpc_object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
1466 if (value == NULL) {
1467 INFO("cti_internal_prefix_reply_callback: prefixes array not present in IPv6:Routes event.");
1468 } else {
1469 status = cti_parse_prefixes_array(&vec, value);
1470 }
1471 }
1472 }
1473 if (callback != NULL) {
1474 callback(conn_ref->context, vec, status);
1475 } else {
1476 INFO("Not calling callback.");
1477 }
1478 if (vec != NULL) {
1479 RELEASE_HERE(vec, cti_prefix_vec_finalize);
1480 }
1481 }
1482
1483 cti_status_t
1484 cti_get_prefix_list(cti_connection_t *ref, void *NULLABLE context, cti_prefix_reply_t NONNULL callback,
1485 dispatch_queue_t NONNULL client_queue)
1486 {
1487 cti_status_t errx;
1488 xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
1489
1490 cti_callback_t app_callback;
1491 app_callback.prefix_reply = callback;
1492
1493 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
1494 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
1495 xpc_dictionary_set_string(dict, "method", "PropGet");
1496 xpc_dictionary_set_string(dict, "property_name", "IPv6:Routes");
1497
1498 errx = setup_for_command(ref, client_queue, "get_prefix_list", "IPv6:Routes", dict, "WpanctlCmd",
1499 context, app_callback, cti_internal_prefix_reply_callback);
1500 xpc_release(dict);
1501
1502 return errx;
1503 }
1504
1505 cti_status_t
1506 cti_events_discontinue(cti_connection_t ref)
1507 {
1508 cti_connection_release(ref);
1509 return kCTIStatus_NoError;
1510 }
1511
1512 // Local Variables:
1513 // mode: C
1514 // tab-width: 4
1515 // c-file-style: "bsd"
1516 // c-basic-offset: 4
1517 // fill-column: 120
1518 // indent-tabs-mode: nil
1519 // End: