1 /* cti-services.c 2 * 3 * Copyright (c) 2020-2024 Apple 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 * https://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 code adds border router support to 3rd party HomeKit Routers as part of Apples commitment to the CHIP project. 18 * 19 * Concise Thread Interface for Thread Border router control. 20 */ 21 22 23 #include <netinet/in.h> 24 #include <string.h> 25 #include <stdlib.h> 26 27 #include <Block.h> 28 #include <os/log.h> 29 #include <netinet/in.h> 30 #include <net/if.h> 31 #include <arpa/inet.h> 32 #include <netinet6/in6_var.h> 33 #include <netinet/icmp6.h> 34 #include <netinet6/nd6.h> 35 #include "xpc_clients.h" 36 #include "cti-services.h" 37 typedef xpc_object_t object_t; 38 typedef void (*cti_internal_callback_t)(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status); 39 40 41 //************************************************************************************************************* 42 // Globals 43 44 #include "cti-common.h" 45 46 static int client_serial_number; 47 48 struct _cti_connection_t 49 { 50 int ref_count; 51 52 // Callback function ptr for Client 53 cti_callback_t callback; 54 55 // xpc_connection between client and daemon 56 xpc_connection_t NULLABLE connection; 57 58 // Before we can send commands, we have to check in, so when starting up, we stash the initial command 59 // here until we get an acknowledgment for the checkin. 60 object_t *first_command; 61 62 // Queue specified by client for scheduling its Callback 63 dispatch_queue_t NULLABLE client_queue; 64 65 // For commands that fetch properties and also track properties, this will contain the name of the property 66 // for which events are requested. 67 const char *property_name; 68 69 // For commands that fetch string properties, this will contain the name of the string property to look for 70 // in the answer 71 const char *return_property_name; 72 73 cti_internal_callback_t NONNULL internal_callback; 74 75 // Client context 76 void *NULLABLE context; 77 78 // Printed when debugging the event handler 79 const char *NONNULL command_name; 80 81 // This connection's serial number, based on the global client_serial_number above. 82 int serial; 83 84 // True if we've gotten a response to the check-in message. 85 bool checked_in; 86 }; 87 88 //************************************************************************************************************* 89 // Utility Functions 90 91 static void 92 cti_connection_finalize(cti_connection_t ref) 93 { 94 if (ref->first_command != NULL) { 95 xpc_release(ref->first_command); 96 ref->first_command = NULL; 97 } 98 free(ref); 99 } 100 101 #define cti_connection_release(ref) cti_connection_release_(ref, __FILE__, __LINE__) 102 static void 103 cti_connection_release_(cti_connection_t ref, const char *file, int line) 104 { 105 ref->callback.reply = NULL; 106 RELEASE(ref, cti_connection); 107 } 108 109 static void 110 cti_xpc_connection_finalize(void *context) 111 { 112 cti_connection_t ref = context; 113 INFO("[CX%d] " PUB_S_SRP, ref->serial, ref->command_name); 114 cti_connection_release(context); 115 } 116 117 static char * 118 cti_xpc_copy_description(object_t object) 119 { 120 xpc_type_t type = xpc_get_type(object); 121 if (type == XPC_TYPE_UINT64) { 122 uint64_t num = xpc_uint64_get_value(object); 123 char buf[23]; 124 snprintf(buf, sizeof buf, "%llu", num); 125 return strdup(buf); 126 } else if (type == XPC_TYPE_INT64) { 127 int64_t num = xpc_int64_get_value(object); 128 char buf[23]; 129 snprintf(buf, sizeof buf, "%lld", num); 130 return strdup(buf); 131 } else if (type == XPC_TYPE_STRING) { 132 const char *str = xpc_string_get_string_ptr(object); 133 size_t len = xpc_string_get_length(object); 134 char *ret = malloc(len + 3); 135 if (ret != NULL) { 136 *ret = '"'; 137 strlcpy(ret + 1, str, len + 1); 138 ret[len + 1] = '"'; 139 ret[len + 2] = 0; 140 return ret; 141 } 142 } else if (type == XPC_TYPE_DATA) { 143 const uint8_t *data = xpc_data_get_bytes_ptr(object); 144 size_t i, len = xpc_data_get_length(object); 145 char *ret = malloc(len * 2 + 3); 146 if (ret != NULL) { 147 ret[0] = '0'; 148 ret[1] = 'x'; 149 for (i = 0; i < len; i++) { 150 snprintf(ret + i * 2, 3, "%02x", data[i]); 151 } 152 return ret; 153 } 154 } else if (type == XPC_TYPE_BOOL) { 155 bool flag = xpc_bool_get_value(object); 156 if (flag) { 157 return strdup("true"); 158 } else { 159 return strdup("false"); 160 } 161 } else if (type == XPC_TYPE_ARRAY) { 162 size_t avail, vlen, len = 0, i, count = xpc_array_get_count(object); 163 char **values = malloc(count * sizeof(*values)); 164 char *ret, *p_ret; 165 if (values == NULL) { 166 return NULL; 167 } 168 xpc_array_apply(object, ^bool (size_t index, object_t value) { 169 values[index] = cti_xpc_copy_description(value); 170 return true; 171 }); 172 for (i = 0; i < count; i++) { 173 if (values[i] == NULL) { 174 len += 6; 175 } else { 176 len += strlen(values[i]) + 2; 177 } 178 } 179 ret = malloc(len + 3); 180 p_ret = ret; 181 avail = len + 1; 182 *p_ret++ = '['; 183 --avail; 184 for (i = 0; i < count; i++) { 185 if (p_ret != NULL) { 186 snprintf(p_ret, avail, "%s%s%s", i == 0 ? "" : " ", values[i] != NULL ? values[i] : "NULL", (i + 1 == count) ? "" : ","); 187 vlen = strlen(p_ret); 188 p_ret += vlen; 189 avail -= vlen; 190 } 191 if (values[i] != NULL) { 192 free(values[i]); 193 } 194 } 195 *p_ret++ = ']'; 196 *p_ret++ = 0; 197 free(values); 198 return ret; 199 } 200 return xpc_copy_description(object); 201 } 202 203 static void 204 cti_log_object(const char *context, int serial, const char *command, const char *preamble, const char *divide, object_t *object, char *indent) 205 { 206 xpc_type_t type = xpc_get_type(object); 207 static char no_indent[] = ""; 208 if (indent == NULL) { 209 indent = no_indent; 210 } 211 char *new_indent; 212 size_t depth; 213 char *desc; 214 char *compound_begin; 215 char *compound_end; 216 217 if (type == XPC_TYPE_DICTIONARY || type == XPC_TYPE_ARRAY) { 218 bool compact = true; 219 bool *p_compact = &compact; 220 if (type == XPC_TYPE_DICTIONARY) { 221 compound_begin = "{"; 222 compound_end = "}"; 223 xpc_dictionary_apply(object, ^bool (const char *__unused key, object_t value) { 224 xpc_type_t sub_type = xpc_get_type(value); 225 if (sub_type == XPC_TYPE_DICTIONARY) { 226 *p_compact = false; 227 } else if (sub_type == XPC_TYPE_ARRAY) { 228 xpc_array_apply(value, ^bool (size_t __unused index, object_t sub_value) { 229 xpc_type_t sub_sub_type = xpc_get_type(sub_value); 230 if (sub_sub_type == XPC_TYPE_DICTIONARY || sub_sub_type == XPC_TYPE_ARRAY) { 231 *p_compact = false; 232 } 233 return true; 234 }); 235 } 236 return true; 237 }); 238 } else { 239 compound_begin = "["; 240 compound_end = "]"; 241 xpc_array_apply(object, ^bool (size_t __unused index, object_t value) { 242 xpc_type_t sub_type = xpc_get_type(value); 243 if (sub_type == XPC_TYPE_DICTIONARY || sub_type == XPC_TYPE_ARRAY) { 244 *p_compact = false; 245 } 246 return true; 247 }); 248 } 249 if (compact) { 250 size_t i, count; 251 const char **keys = NULL; 252 char **values; 253 char linebuf[160], *p_space; 254 size_t space_avail = sizeof(linebuf); 255 bool first = true; 256 257 if (type == XPC_TYPE_DICTIONARY) { 258 count = xpc_dictionary_get_count(object); 259 } else { 260 count = xpc_array_get_count(object); 261 } 262 263 values = malloc(count * sizeof(*values)); 264 if (values == NULL) { 265 INFO("[CX%d] no memory", serial); 266 return; 267 } 268 if (type == XPC_TYPE_DICTIONARY) { 269 int index = 0, *p_index = &index; 270 keys = malloc(count * sizeof(*keys)); 271 if (keys == NULL) { 272 free(values); 273 INFO("[CX%d] no memory", serial); 274 } 275 xpc_dictionary_apply(object, ^bool (const char *key, object_t value) { 276 values[*p_index] = cti_xpc_copy_description(value); 277 keys[*p_index] = key; 278 (*p_index)++; 279 return true; 280 }); 281 } else { 282 xpc_array_apply(object, ^bool (size_t index, object_t value) { 283 values[index] = cti_xpc_copy_description(value); 284 return true; 285 }); 286 } 287 p_space = linebuf; 288 for (i = 0; i < count; i++) { 289 char *str = values[i]; 290 size_t len; 291 char *eol = ""; 292 bool emitted = false; 293 if (str == NULL) { 294 str = "NULL"; 295 len = 6; 296 } else { 297 len = strlen(str) + 2; 298 } 299 if (type == XPC_TYPE_DICTIONARY) { 300 #ifdef __clang_analyzer__ 301 len = 2; 302 #else 303 len += strlen(keys[i]) + 2; // "key: " 304 #endif 305 } 306 if (len + 1 > space_avail) { 307 if (i + 1 == count) { 308 eol = compound_end; 309 } 310 if (space_avail != sizeof(linebuf)) { 311 if (first) { 312 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP 313 PUB_S_SRP PUB_S_SRP, serial, context, command, 314 indent, preamble, divide, compound_begin, linebuf, eol); 315 first = false; 316 } else { 317 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP 318 PUB_S_SRP, serial, context, command, 319 indent, preamble, divide, linebuf, eol); 320 } 321 space_avail = sizeof linebuf; 322 p_space = linebuf; 323 } 324 if (len + 1 > space_avail) { 325 if (type == XPC_TYPE_DICTIONARY) { 326 #ifndef __clang_analyzer__ 327 if (first) { 328 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP 329 PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP, serial, context, command, 330 indent, preamble, divide, compound_begin, keys[i], str, eol); 331 first = false; 332 } else { 333 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP 334 ": " PUB_S_SRP PUB_S_SRP, serial, context, command, 335 indent, preamble, divide, keys[i], str, eol); 336 } 337 #endif 338 } else { 339 if (first) { 340 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP 341 PUB_S_SRP PUB_S_SRP, serial, context, command, 342 indent, preamble, divide, compound_begin, str, eol); 343 first = false; 344 } else { 345 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP 346 PUB_S_SRP, serial, context, command, indent, preamble, divide, str, eol); 347 } 348 } 349 emitted = true; 350 } 351 } 352 if (!emitted) { 353 if (type == XPC_TYPE_DICTIONARY) { 354 #ifndef __clang_analyzer__ 355 snprintf(p_space, space_avail, "%s%s: %s%s", i == 0 ? "" : " ", keys[i], str, i + 1 == count ? "" : ","); 356 #endif 357 } else { 358 snprintf(p_space, space_avail, "%s%s%s", i == 0 ? "" : " ", str, i + 1 == count ? "" : ","); 359 } 360 len = strlen(p_space); 361 p_space += len; 362 space_avail -= len; 363 } 364 if (values[i] != NULL) { 365 free(values[i]); 366 values[i] = NULL; 367 } 368 } 369 if (linebuf != p_space) { 370 if (first) { 371 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP 372 PUB_S_SRP, serial, context, command, 373 indent, preamble, divide, compound_begin, linebuf, compound_end); 374 } else { 375 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " + " PUB_S_SRP PUB_S_SRP, 376 serial, context, command, indent, preamble, divide, linebuf, compound_end); 377 } 378 } 379 free(values); 380 if (keys != NULL) { 381 free(keys); 382 } 383 } else { 384 depth = strlen(indent); 385 new_indent = malloc(depth + 3); 386 if (new_indent == NULL) { 387 new_indent = indent; 388 } else { 389 memset(new_indent, ' ', depth + 2); 390 new_indent[depth + 2] = 0; 391 } 392 if (type == XPC_TYPE_DICTIONARY) { 393 xpc_dictionary_apply(object, ^bool (const char *key, object_t value) { 394 cti_log_object(context, serial, command, key, ": ", value, new_indent); 395 return true; 396 }); 397 } else { 398 xpc_array_apply(object, ^bool (size_t index, object_t value) { 399 char numbuf[23]; 400 snprintf(numbuf, sizeof(numbuf), "%zd", index); 401 cti_log_object(context, serial, command, numbuf, ": ", value, new_indent); 402 return true; 403 }); 404 } 405 if (new_indent != indent) { 406 free(new_indent); 407 } 408 } 409 } else { 410 desc = cti_xpc_copy_description(object); 411 INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP, 412 serial, context, command, indent, preamble, divide, desc); 413 free(desc); 414 } 415 } 416 417 static void 418 cti_event_handler(object_t event, cti_connection_t conn_ref) 419 { 420 if (event == XPC_ERROR_CONNECTION_INVALID) { 421 INFO("[CX%d] (" PUB_S_SRP "): cleanup", conn_ref->serial, conn_ref->command_name); 422 if (conn_ref->callback.reply != NULL) { 423 conn_ref->internal_callback(conn_ref, event, kCTIStatus_Disconnected); 424 } else { 425 INFO("[CX%d] No callback", conn_ref->serial); 426 } 427 if (conn_ref->connection != NULL) { 428 INFO("[CX%d] releasing connection %p", conn_ref->serial, conn_ref->connection); 429 xpc_release(conn_ref->connection); 430 conn_ref->connection = NULL; 431 } 432 return; 433 } 434 435 if (conn_ref->connection == NULL) { 436 cti_log_object("cti_event_handler NULL connection", 437 conn_ref->serial, conn_ref->command_name, "", "", event, ""); 438 return; 439 } 440 441 if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) { 442 cti_log_object("cti_event_handler", conn_ref->serial, conn_ref->command_name, "", "", event, ""); 443 if (!conn_ref->checked_in) { 444 object_t command_result = xpc_dictionary_get_value(event, "commandResult"); 445 int status = 0; 446 if (command_result != NULL) { 447 status = (int)xpc_int64_get_value(command_result); 448 if (status == 0) { 449 object_t command_data = xpc_dictionary_get_value(event, "commandData"); 450 if (command_data == NULL) { 451 status = 0; 452 } else { 453 object_t ret_value = xpc_dictionary_get_value(command_data, "ret"); 454 if (ret_value == NULL) { 455 status = 0; 456 } else { 457 status = (int)xpc_int64_get_value(ret_value); 458 } 459 } 460 } 461 } 462 463 if (status != 0) { 464 conn_ref->internal_callback(conn_ref, event, kCTIStatus_UnknownError); 465 INFO("[CX%d] canceling xpc connection %p", conn_ref->serial, conn_ref->connection); 466 xpc_connection_cancel(conn_ref->connection); 467 } else if (conn_ref->property_name != NULL) { 468 // We're meant to both get the property and subscribe to events on it. 469 object_t *dict = xpc_dictionary_create(NULL, NULL, 0); 470 if (dict == NULL) { 471 ERROR("[CX%d] cti_event_handler(" PUB_S_SRP "): no memory, canceling %p.", 472 conn_ref->serial, conn_ref->command_name, conn_ref->connection); 473 xpc_connection_cancel(conn_ref->connection); 474 } else { 475 object_t *array = xpc_array_create(NULL, 0); 476 if (array == NULL) { 477 ERROR("[CX%d] cti_event_handler(" PUB_S_SRP "): no memory, canceling %p.", 478 conn_ref->serial, conn_ref->command_name, conn_ref->connection); 479 xpc_connection_cancel(conn_ref->connection); 480 } else { 481 xpc_dictionary_set_string(dict, "command", "eventsOn"); 482 xpc_dictionary_set_string(dict, "clientName", "srp-mdns-proxy"); 483 xpc_dictionary_set_value(dict, "eventList", array); 484 xpc_array_set_string(array, XPC_ARRAY_APPEND, conn_ref->property_name); 485 conn_ref->property_name = NULL; 486 cti_log_object("cti_event_handler/events on", 487 conn_ref->serial, conn_ref->command_name, "", "", dict, ""); 488 INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection); 489 xpc_connection_send_message_with_reply(conn_ref->connection, dict, conn_ref->client_queue, 490 ^(object_t in_event) { 491 cti_event_handler(in_event, conn_ref); 492 }); 493 xpc_release(array); 494 } 495 xpc_release(dict); 496 } 497 } else { 498 object_t *message = conn_ref->first_command; 499 conn_ref->first_command = NULL; 500 cti_log_object("cti_event_handler/command is", 501 conn_ref->serial, conn_ref->command_name, "", "", message, ""); 502 conn_ref->checked_in = true; 503 504 INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection); 505 xpc_connection_send_message_with_reply(conn_ref->connection, message, conn_ref->client_queue, 506 ^(object_t in_event) { 507 cti_event_handler(in_event, conn_ref); 508 }); 509 xpc_release(message); 510 } 511 } else { 512 conn_ref->internal_callback(conn_ref, event, kCTIStatus_NoError); 513 } 514 } else { 515 cti_log_object("cti_event_handler/other", conn_ref->serial, conn_ref->command_name, "", "", event, ""); 516 ERROR("[CX%d] cti_event_handler: Unexpected Connection Error [" PUB_S_SRP "]", 517 conn_ref->serial, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); 518 conn_ref->internal_callback(conn_ref, NULL, kCTIStatus_DaemonNotRunning); 519 if (event != XPC_ERROR_CONNECTION_INTERRUPTED) { 520 INFO("[CX%d] canceling xpc connection %p", conn_ref->serial, conn_ref->connection); 521 xpc_connection_cancel(conn_ref->connection); 522 } 523 } 524 } 525 526 // Creates a new cti_ Connection Reference(cti_connection_t) 527 static cti_status_t 528 init_connection(cti_connection_t *ref, const char *servname, object_t *dict, const char *command_name, 529 const char *property_name, const char *return_property_name, void *context, cti_callback_t app_callback, 530 cti_internal_callback_t internal_callback, run_context_t client_queue, 531 const char *file, int line) 532 { 533 // Use an cti_connection_t on the stack to be captured in the blocks below, rather than 534 // capturing the cti_connection_t* owned by the client 535 #ifdef MALLOC_DEBUG_LOGGING 536 cti_connection_t conn_ref = debug_calloc(1, sizeof(struct _cti_connection_t), file, line); 537 #else 538 cti_connection_t conn_ref = calloc(1, sizeof(struct _cti_connection_t)); 539 #endif 540 if (conn_ref == NULL) { 541 ERROR("no memory to allocate!"); 542 return kCTIStatus_NoMemory; 543 } 544 conn_ref->serial = client_serial_number; 545 client_serial_number++; 546 547 // We always retain a reference for the caller, even if the caller doesn't actually hold the reference. 548 // Calls that do not result in repeated callbacks release this reference after calling the callback. 549 // Such calls do not return a reference to the caller, so there is no chance of a double release. 550 // Calls that result in repeated callbacks have to release the reference by calling cti_events_discontinue. 551 // If this isn't done, the reference will never be released. 552 RETAIN(conn_ref, cti_connection); 553 554 if (client_queue == NULL) { 555 client_queue = dispatch_get_main_queue(); 556 } 557 558 // Initialize the cti_connection_t 559 dispatch_retain(client_queue); 560 conn_ref->command_name = command_name; 561 conn_ref->property_name = property_name; 562 conn_ref->return_property_name = return_property_name; 563 conn_ref->context = context; 564 conn_ref->client_queue = client_queue; 565 conn_ref->callback = app_callback; 566 conn_ref->internal_callback = internal_callback; 567 conn_ref->connection = xpc_connection_create_mach_service(servname, conn_ref->client_queue, 568 XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); 569 INFO("[CX%d] xpc connection: %p", conn_ref->serial, conn_ref->connection); 570 conn_ref->first_command = dict; 571 xpc_retain(dict); 572 573 cti_log_object("init_connection/command", conn_ref->serial, conn_ref->command_name, "", "", dict, ""); 574 575 if (conn_ref->connection == NULL) 576 { 577 ERROR("conn_ref/lib_q is NULL"); 578 if (conn_ref != NULL) { 579 RELEASE_HERE(conn_ref, cti_connection); 580 } 581 return kCTIStatus_NoMemory; 582 } 583 584 RETAIN_HERE(conn_ref, cti_connection); // For the event handler. 585 xpc_connection_set_event_handler(conn_ref->connection, ^(object_t event) { cti_event_handler(event, conn_ref); }); 586 xpc_connection_set_finalizer_f(conn_ref->connection, cti_xpc_connection_finalize); 587 xpc_connection_set_context(conn_ref->connection, conn_ref); 588 xpc_connection_resume(conn_ref->connection); 589 590 char srp_name[] = "srp-mdns-proxy"; 591 char client_name[sizeof(srp_name) + 20]; 592 snprintf(client_name, sizeof client_name, "%s-%d", srp_name, conn_ref->serial); 593 594 object_t checkin_command = xpc_dictionary_create(NULL, NULL, 0); 595 596 xpc_dictionary_set_string(checkin_command, "command", "checkIn"); 597 xpc_dictionary_set_string(checkin_command, "clientName", client_name); 598 599 cti_log_object("init_connection/checkin", conn_ref->serial, conn_ref->command_name, "", "", checkin_command, ""); 600 INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection); 601 xpc_connection_send_message_with_reply(conn_ref->connection, checkin_command, conn_ref->client_queue, 602 ^(object_t event) { cti_event_handler(event, conn_ref); }); 603 604 xpc_release(checkin_command); 605 if (ref) { 606 *ref = conn_ref; 607 } 608 return kCTIStatus_NoError; 609 } 610 611 static cti_status_t 612 setup_for_command(cti_connection_t *ref, run_context_t client_queue, const char *command_name, 613 const char *property_name, const char *return_property_name, object_t dict, const char *command, 614 void *context, cti_callback_t app_callback, cti_internal_callback_t internal_callback, 615 bool events_only, const char *file, int line) 616 { 617 cti_status_t errx = kCTIStatus_NoError; 618 619 // Sanity Checks 620 if (app_callback.reply == NULL || internal_callback == NULL) 621 { 622 ERROR(PUB_S_SRP ": NULL cti_connection_t OR Callback OR Client_Queue parameter", command_name); 623 return kCTIStatus_BadParam; 624 } 625 626 // Get conn_ref from init_connection() 627 if (events_only) { 628 xpc_dictionary_set_string(dict, "command", "eventsOn"); 629 object_t *array = xpc_array_create(NULL, 0); 630 if (array != NULL) { 631 xpc_array_set_string(array, XPC_ARRAY_APPEND, property_name); 632 xpc_dictionary_set_value(dict, "eventList", array); 633 property_name = NULL; 634 xpc_release(array); 635 } else { 636 return kCTIStatus_NoMemory; 637 } 638 } else { 639 xpc_dictionary_set_string(dict, "command", command); 640 } 641 642 errx = init_connection(ref, "com.apple.wpantund.xpc", dict, command_name, property_name, return_property_name, 643 context, app_callback, internal_callback, client_queue, file, line); 644 if (errx) // On error init_connection() leaves *conn_ref set to NULL 645 { 646 ERROR(PUB_S_SRP ": Since init_connection() returned %d error returning w/o sending msg", command_name, errx); 647 return errx; 648 } 649 650 return errx; 651 } 652 653 static void 654 cti_internal_event_reply_callback(cti_connection_t NONNULL conn_ref, object_t __unused reply, cti_status_t status) 655 { 656 cti_reply_t callback; 657 INFO("[CX%d] conn_ref = %p", conn_ref != NULL ? conn_ref->serial : 0, conn_ref); 658 callback = conn_ref->callback.reply; 659 if (callback != NULL) { 660 callback(conn_ref->context, status); 661 } 662 } 663 664 static void 665 cti_internal_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status) 666 { 667 cti_internal_event_reply_callback(conn_ref, reply, status); 668 conn_ref->callback.reply = NULL; 669 if (conn_ref->connection != NULL) { 670 INFO("[CX%d] canceling connection %p", conn_ref->serial, conn_ref->connection); 671 xpc_connection_cancel(conn_ref->connection); 672 } 673 cti_connection_release(conn_ref); 674 } 675 676 cti_status_t 677 cti_add_service_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue, 678 uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length, 679 const uint8_t *server_data, size_t server_data_length, const char *file, int line) 680 { 681 cti_callback_t app_callback; 682 app_callback.reply = callback; 683 cti_status_t errx; 684 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 685 686 xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length); 687 if (server_data != NULL) { 688 xpc_dictionary_set_data(dict, "server_data", server_data, server_data_length); 689 } 690 xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number); 691 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 692 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 693 xpc_dictionary_set_string(dict, "method", "ServiceAdd"); 694 xpc_dictionary_set_bool(dict, "stable", true); 695 696 errx = setup_for_command(NULL, client_queue, "add_service", NULL, NULL, dict, "WpanctlCmd", 697 context, app_callback, cti_internal_reply_callback, false, file, line); 698 xpc_release(dict); 699 700 return errx; 701 } 702 703 cti_status_t 704 cti_remove_service_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue, 705 uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length, 706 const char *file, int line) 707 { 708 cti_callback_t app_callback; 709 app_callback.reply = callback; 710 cti_status_t errx; 711 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 712 713 xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length); 714 xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number); 715 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 716 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 717 xpc_dictionary_set_string(dict, "method", "ServiceRemove"); 718 719 errx = setup_for_command(NULL, client_queue, "remove_service", NULL, NULL, dict, "WpanctlCmd", 720 context, app_callback, cti_internal_reply_callback, false, file, line); 721 xpc_release(dict); 722 723 return errx; 724 } 725 726 static cti_status_t 727 cti_do_prefix(void *context, cti_reply_t callback, run_context_t client_queue, 728 struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable, bool adding, 729 int priority, const char *file, int line) 730 { 731 cti_callback_t app_callback; 732 app_callback.reply = callback; 733 cti_status_t errx; 734 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 735 736 if (dict == NULL) { 737 ERROR("cti_do_prefix: no memory for command dictionary."); 738 return kCTIStatus_NoMemory; 739 } 740 xpc_dictionary_set_bool(dict, "preferred", preferred); 741 if (adding) { 742 xpc_dictionary_set_uint64(dict, "preferredLifetime", ND6_INFINITE_LIFETIME); 743 xpc_dictionary_set_uint64(dict, "validLifetime", ND6_INFINITE_LIFETIME); 744 } else { 745 xpc_dictionary_set_uint64(dict, "preferredLifetime", 0); 746 xpc_dictionary_set_uint64(dict, "validLifetime", 0); 747 } 748 xpc_dictionary_set_int64(dict, "prefix_length", 16); 749 xpc_dictionary_set_bool(dict, "dhcp", false); 750 xpc_dictionary_set_data(dict, "prefix", prefix, sizeof(*prefix)); 751 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 752 xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length); 753 xpc_dictionary_set_bool(dict, "slaac", slaac); 754 xpc_dictionary_set_bool(dict, "onMesh", on_mesh); 755 xpc_dictionary_set_bool(dict, "configure", false); 756 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 757 xpc_dictionary_set_string(dict, "method", "ConfigGateway"); 758 xpc_dictionary_set_bool(dict, "stable", stable); 759 xpc_dictionary_set_bool(dict, "defaultRoute", true); 760 xpc_dictionary_set_int64(dict, "priority", priority); 761 762 errx = setup_for_command(NULL, client_queue, adding ? "add_prefix" : "remove_prefix", NULL, NULL, dict, "WpanctlCmd", 763 context, app_callback, cti_internal_reply_callback, false, file, line); 764 xpc_release(dict); 765 766 return errx; 767 } 768 769 cti_status_t 770 cti_add_prefix_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue, 771 struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable, 772 int priority, const char *file, int line) 773 { 774 return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, on_mesh, preferred, slaac, stable, 775 true, priority, file, line); 776 } 777 778 cti_status_t 779 cti_remove_prefix_(srp_server_t *UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, 780 run_context_t NULLABLE client_queue, struct in6_addr *NONNULL prefix, int prefix_length, 781 const char *file, int line) 782 { 783 return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, false, false, false, false, false, 0, 784 file, line); 785 } 786 787 cti_status_t 788 cti_add_route_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue, 789 struct in6_addr *prefix, int prefix_length, int priority, int domain_id, 790 bool stable, bool nat64, const char *file, int line) 791 { 792 cti_callback_t app_callback; 793 app_callback.reply = callback; 794 cti_status_t errx; 795 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 796 797 if (dict == NULL) { 798 ERROR("cti_do_prefix: no memory for command dictionary."); 799 return kCTIStatus_NoMemory; 800 } 801 xpc_dictionary_set_string(dict, "method", "RouteAdd"); 802 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 803 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 804 xpc_dictionary_set_uint64(dict, "prefix_bits_len", prefix_length); 805 xpc_dictionary_set_data(dict, "prefix_bits", prefix, sizeof(*prefix)); 806 xpc_dictionary_set_uint64(dict, "domain_id", domain_id); 807 xpc_dictionary_set_int64(dict, "priority", priority); 808 xpc_dictionary_set_bool(dict, "stable", stable); 809 xpc_dictionary_set_bool(dict, "nat64", nat64); 810 811 errx = setup_for_command(NULL, client_queue, "add_route", NULL, NULL, dict, "WpanctlCmd", 812 context, app_callback, cti_internal_reply_callback, false, file, line); 813 xpc_release(dict); 814 815 return errx; 816 } 817 818 cti_status_t 819 cti_remove_route_(srp_server_t *UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, 820 run_context_t NULLABLE client_queue, struct in6_addr *NONNULL prefix, int prefix_length, 821 int domain_id, const char *file, int line) 822 { 823 cti_callback_t app_callback; 824 app_callback.reply = callback; 825 cti_status_t errx; 826 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 827 828 if (dict == NULL) { 829 ERROR("cti_do_prefix: no memory for command dictionary."); 830 return kCTIStatus_NoMemory; 831 } 832 xpc_dictionary_set_string(dict, "method", "RouteRemove"); 833 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 834 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 835 xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length); 836 xpc_dictionary_set_data(dict, "prefix_bytes", prefix, sizeof(*prefix)); 837 xpc_dictionary_set_uint64(dict, "domain_id", domain_id); 838 839 errx = setup_for_command(NULL, client_queue, "remove_route", NULL, NULL, dict, "WpanctlCmd", 840 context, app_callback, cti_internal_reply_callback, false, file, line); 841 xpc_release(dict); 842 843 return errx; 844 } 845 846 static void 847 cti_internal_string_event_reply(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 848 { 849 cti_string_property_reply_t callback = conn_ref->callback.string_property_reply; 850 xpc_retain(reply); 851 cti_status_t status = status_in; 852 const char *string_value = NULL; 853 if (status == kCTIStatus_NoError) { 854 // Initial reply (events do a get as well as subscribing to events, and the responses look different. 855 xpc_object_t command_result_value = xpc_dictionary_get_value(reply, "commandResult"); 856 if (command_result_value != NULL) { 857 uint64_t command_result = xpc_int64_get_value(command_result_value); 858 if (command_result != 0) { 859 ERROR("[CX%d] nonzero result %llu", conn_ref->serial, command_result); 860 status = kCTIStatus_UnknownError; 861 } else { 862 object_t result_dictionary = xpc_dictionary_get_dictionary(reply, "commandData"); 863 if (status == kCTIStatus_NoError) { 864 if (result_dictionary != NULL) { 865 const char *property_name = xpc_dictionary_get_string(result_dictionary, "property_name"); 866 if (property_name == NULL || strcmp(property_name, conn_ref->return_property_name)) { 867 status = kCTIStatus_UnknownError; 868 } else { 869 string_value = xpc_dictionary_get_string(result_dictionary, "value"); 870 if (string_value == NULL) { 871 status = kCTIStatus_UnknownError; 872 } 873 } 874 } else { 875 status = kCTIStatus_UnknownError; 876 } 877 } 878 } 879 } else { 880 xpc_object_t event_data_dict = xpc_dictionary_get_dictionary(reply, "eventData"); 881 if (event_data_dict == NULL) { 882 ERROR("[CX%d] no eventData dictionary", conn_ref->serial); 883 status = kCTIStatus_UnknownError; 884 } else { 885 // Event data looks like { "v_type" : 13, "path": <string>, "value": <string>, "key": <property_name> } 886 xpc_object_t value_array = xpc_dictionary_get_array(event_data_dict, "value"); 887 if (value_array == NULL) { 888 ERROR("[CX%d] eventData dictionary contains no 'value' key", conn_ref->serial); 889 status = kCTIStatus_UnknownError; 890 } else { 891 size_t count = xpc_array_get_count(value_array); 892 if (count < 1) { 893 ERROR("[CX%d] eventData value array has no elements", conn_ref->serial); 894 status = kCTIStatus_UnknownError; 895 } else { 896 if (count > 1) { 897 ERROR("[CX%d] eventData value array has %zd elements", conn_ref->serial, count); 898 // This is weird, but we're not going to deliberately fail because of it. 899 } 900 string_value = xpc_array_get_string(value_array, 0); 901 if (string_value == NULL) { 902 ERROR("[CX%d] eventData value array's first element is not a string", conn_ref->serial); 903 status = kCTIStatus_UnknownError; 904 } 905 } 906 } 907 } 908 } 909 } 910 if (callback != NULL) { 911 callback(conn_ref->context, string_value, status); 912 } 913 xpc_release(reply); 914 } 915 916 static void 917 cti_internal_string_property_reply(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 918 { 919 cti_internal_string_event_reply(conn_ref, reply, status_in); 920 conn_ref->callback.reply = NULL; 921 if (conn_ref->connection != NULL) { 922 INFO("[CX%d] canceling connection %p", conn_ref->serial, conn_ref->connection); 923 xpc_connection_cancel(conn_ref->connection); 924 } 925 RELEASE_HERE(conn_ref, cti_connection); 926 } 927 928 cti_status_t 929 cti_get_tunnel_name_(srp_server_t *UNUSED server, void *NULLABLE context, cti_string_property_reply_t NONNULL callback, 930 run_context_t NULLABLE client_queue, const char *file, int line) 931 { 932 cti_callback_t app_callback; 933 app_callback.string_property_reply = callback; 934 cti_status_t errx; 935 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 936 937 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 938 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 939 xpc_dictionary_set_string(dict, "method", "PropGet"); 940 xpc_dictionary_set_string(dict, "property_name", "Config:TUN:InterfaceName"); 941 942 errx = setup_for_command(NULL, client_queue, "get_tunnel_name", NULL, "Config:TUN:InterfaceName", dict, "WpanctlCmd", 943 context, app_callback, cti_internal_string_property_reply, false, file, line); 944 xpc_release(dict); 945 946 return errx; 947 } 948 949 cti_status_t 950 cti_track_active_data_set_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 951 cti_reply_t NONNULL callback, 952 run_context_t NULLABLE client_queue, const char *file, int line) 953 { 954 cti_callback_t app_callback; 955 app_callback.reply = callback; 956 cti_status_t errx; 957 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 958 959 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 960 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 961 962 errx = setup_for_command(ref, client_queue, "track_active_data_set", "ActiveDataSetChanged", NULL, dict, "WpanctlCmd", 963 context, app_callback, cti_internal_event_reply_callback, true, file, line); 964 xpc_release(dict); 965 966 return errx; 967 } 968 969 cti_status_t 970 cti_get_mesh_local_prefix_(srp_server_t *UNUSED server, void *NULLABLE context, 971 cti_string_property_reply_t NONNULL callback, 972 run_context_t NULLABLE client_queue, const char *file, int line) 973 { 974 cti_callback_t app_callback; 975 app_callback.string_property_reply = callback; 976 cti_status_t errx; 977 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 978 979 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 980 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 981 xpc_dictionary_set_string(dict, "method", "PropGet"); 982 xpc_dictionary_set_string(dict, "property_name", "IPv6:MeshLocalPrefix"); 983 984 errx = setup_for_command(NULL, client_queue, "get_mesh_local_prefix", NULL, 985 "IPv6:MeshLocalPrefix", dict, "WpanctlCmd", 986 context, app_callback, cti_internal_string_property_reply, false, file, line); 987 xpc_release(dict); 988 989 return errx; 990 } 991 992 cti_status_t 993 cti_get_mesh_local_address_(srp_server_t *UNUSED server, void *NULLABLE context, 994 cti_string_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 995 const char *file, int line) 996 { 997 cti_callback_t app_callback; 998 app_callback.string_property_reply = callback; 999 cti_status_t errx; 1000 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1001 1002 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1003 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1004 xpc_dictionary_set_string(dict, "method", "PropGet"); 1005 xpc_dictionary_set_string(dict, "property_name", "IPv6:MeshLocalAddress"); 1006 1007 errx = setup_for_command(NULL, client_queue, "get_mesh_local_address", NULL, 1008 "IPv6:MeshLocalAddress", dict, "WpanctlCmd", context, app_callback, 1009 cti_internal_string_property_reply, false, file, line); 1010 xpc_release(dict); 1011 1012 return errx; 1013 } 1014 1015 static cti_status_t 1016 cti_event_or_response_extract(object_t *reply, object_t *result_dictionary) 1017 { 1018 object_t *result = xpc_dictionary_get_dictionary(reply, "commandData"); 1019 if (result == NULL) { 1020 result = xpc_dictionary_get_dictionary(reply, "eventData"); 1021 } else { 1022 int command_status = (int)xpc_dictionary_get_int64(reply, "commandResult"); 1023 if (command_status != 0) { 1024 INFO("nonzero status %d", command_status); 1025 return kCTIStatus_UnknownError; 1026 } 1027 } 1028 if (result != NULL) { 1029 *result_dictionary = result; 1030 return kCTIStatus_NoError; 1031 } 1032 INFO("null result"); 1033 return kCTIStatus_UnknownError; 1034 } 1035 1036 static void 1037 cti_internal_state_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 1038 { 1039 cti_state_reply_t callback = conn_ref->callback.state_reply; 1040 cti_network_state_t state = kCTI_NCPState_Unknown; 1041 cti_status_t status = status_in; 1042 if (status == kCTIStatus_NoError) { 1043 object_t result_dictionary = NULL; 1044 status = cti_event_or_response_extract(reply, &result_dictionary); 1045 if (status == kCTIStatus_NoError) { 1046 const char *state_name = xpc_dictionary_get_string(result_dictionary, "value"); 1047 if (state_name == NULL) { 1048 status = kCTIStatus_UnknownError; 1049 } else if (!strcmp(state_name, "uninitialized")) { 1050 state = kCTI_NCPState_Uninitialized; 1051 } else if (!strcmp(state_name, "uninitialized:fault")) { 1052 state = kCTI_NCPState_Fault; 1053 } else if (!strcmp(state_name, "uninitialized:upgrading")) { 1054 state = kCTI_NCPState_Upgrading; 1055 } else if (!strcmp(state_name, "offline:deep-sleep")) { 1056 state = kCTI_NCPState_DeepSleep; 1057 } else if (!strcmp(state_name, "offline")) { 1058 state = kCTI_NCPState_Offline; 1059 } else if (!strcmp(state_name, "offline:commissioned")) { 1060 state = kCTI_NCPState_Commissioned; 1061 } else if (!strcmp(state_name, "associating")) { 1062 state = kCTI_NCPState_Associating; 1063 } else if (!strcmp(state_name, "associating:credentials-needed")) { 1064 state = kCTI_NCPState_CredentialsNeeded; 1065 } else if (!strcmp(state_name, "associated")) { 1066 state = kCTI_NCPState_Associated; 1067 } else if (!strcmp(state_name, "associated:no-parent")) { 1068 state = kCTI_NCPState_Isolated; 1069 } else if (!strcmp(state_name, "associated:netwake-asleep")) { 1070 state = kCTI_NCPState_NetWake_Asleep; 1071 } else if (!strcmp(state_name, "associated:netwake-waking")) { 1072 state = kCTI_NCPState_NetWake_Waking; 1073 } 1074 } 1075 } 1076 if (callback != NULL) { 1077 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 1078 callback(conn_ref->context, state, status); 1079 } 1080 } 1081 1082 cti_status_t 1083 cti_get_state_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 1084 cti_state_reply_t NONNULL callback, run_context_t NULLABLE client_queue, const char *file, int line) 1085 { 1086 cti_callback_t app_callback; 1087 app_callback.state_reply = callback; 1088 cti_status_t errx; 1089 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1090 1091 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1092 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1093 xpc_dictionary_set_string(dict, "method", "PropGet"); 1094 xpc_dictionary_set_string(dict, "property_name", "NCP:State"); 1095 1096 errx = setup_for_command(ref, client_queue, "get_state", "NCP:State", NULL, dict, "WpanctlCmd", 1097 context, app_callback, cti_internal_state_reply_callback, false, file, line); 1098 xpc_release(dict); 1099 1100 return errx; 1101 } 1102 1103 typedef const char *cti_property_name_t; 1104 1105 static void 1106 cti_internal_uint64_property_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 1107 { 1108 cti_uint64_property_reply_t callback = conn_ref->callback.uint64_property_reply; 1109 uint64_t uint64_val = 0; 1110 cti_status_t status = status_in; 1111 if (status == kCTIStatus_NoError) { 1112 object_t result_dictionary = NULL; 1113 status = cti_event_or_response_extract(reply, &result_dictionary); 1114 if (status == kCTIStatus_NoError) { 1115 object_t value = xpc_dictionary_get_value(result_dictionary, "value"); 1116 if (value == NULL) { 1117 ERROR("[CX%d] no property value.", conn_ref->serial); 1118 } else { 1119 if (xpc_get_type(value) == XPC_TYPE_UINT64) { 1120 uint64_val = xpc_dictionary_get_uint64(result_dictionary, "value"); 1121 } else if (xpc_get_type(value) == XPC_TYPE_ARRAY) { 1122 size_t count = xpc_array_get_count(value); 1123 if (count != sizeof(uint64_val)) { 1124 goto fail; 1125 } 1126 for (size_t i = 0; i < sizeof(uint64_val); i++) { 1127 object_t element = xpc_array_get_value(value, i); 1128 if (xpc_get_type(element) != XPC_TYPE_UINT64) { 1129 goto fail; 1130 } 1131 uint64_t ev = xpc_array_get_uint64(value, i); 1132 if (ev > 255) { 1133 goto fail; 1134 } 1135 uint64_val = (uint64_val << 8) | ev; 1136 } 1137 } else { 1138 char *value_string; 1139 fail: 1140 value_string = xpc_copy_description(value); 1141 ERROR("[CX%d] property value is " PUB_S_SRP " instead of uint64_t or array of uint64_t byte values.", 1142 conn_ref->serial, value_string); 1143 free(value_string); 1144 status = kCTIStatus_Invalid; 1145 } 1146 } 1147 } 1148 } 1149 if (callback != NULL) { 1150 callback(conn_ref->context, uint64_val, status); 1151 } 1152 } 1153 1154 static cti_status_t 1155 cti_get_uint64_property(cti_connection_t *ref, void *NULLABLE context, cti_uint64_property_reply_t NONNULL callback, 1156 run_context_t NULLABLE client_queue, cti_property_name_t property_name, const char *file, int line) 1157 { 1158 cti_callback_t app_callback; 1159 app_callback.uint64_property_reply = callback; 1160 cti_status_t errx; 1161 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1162 1163 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1164 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1165 xpc_dictionary_set_string(dict, "method", "PropGet"); 1166 xpc_dictionary_set_string(dict, "property_name", property_name); 1167 1168 errx = setup_for_command(ref, client_queue, "get_uint64_property", property_name, NULL, dict, "WpanctlCmd", 1169 context, app_callback, cti_internal_uint64_property_callback, false, file, line); 1170 xpc_release(dict); 1171 1172 return errx; 1173 } 1174 1175 cti_status_t 1176 cti_get_partition_id_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context, 1177 cti_uint64_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 1178 const char *NONNULL file, int line) 1179 { 1180 return cti_get_uint64_property(ref, context, callback, client_queue, kCTIPropertyPartitionID, file, line); 1181 } 1182 1183 cti_status_t 1184 cti_get_extended_pan_id_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context, 1185 cti_uint64_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 1186 const char *NONNULL file, int line) 1187 { 1188 return cti_get_uint64_property(ref, context, callback, client_queue, kCTIPropertyExtendedPANID, file, line); 1189 } 1190 1191 static void 1192 cti_internal_network_node_type_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 1193 { 1194 cti_network_node_type_reply_t callback = conn_ref->callback.network_node_type_reply; 1195 cti_network_node_type_t network_node_type = kCTI_NetworkNodeType_Unknown; 1196 cti_status_t status = status_in; 1197 if (status == kCTIStatus_NoError) { 1198 object_t result_dictionary = NULL; 1199 status = cti_event_or_response_extract(reply, &result_dictionary); 1200 if (status == kCTIStatus_NoError) { 1201 object_t value = xpc_dictionary_get_value(result_dictionary, "value"); 1202 if (value == NULL) { 1203 ERROR("[CX%d] No node type returned.", conn_ref->serial); 1204 } else if (xpc_get_type(value) != XPC_TYPE_STRING) { 1205 char *value_string = xpc_copy_description(value); 1206 ERROR("[CX%d] node type type is " PUB_S_SRP " instead of string.", conn_ref->serial, value_string); 1207 free(value_string); 1208 } else { 1209 const char *node_type_name = xpc_dictionary_get_string(result_dictionary, "value"); 1210 if (!strcmp(node_type_name, "unknown")) { 1211 network_node_type = kCTI_NetworkNodeType_Unknown; 1212 } else if (!strcmp(node_type_name, "router")) { 1213 network_node_type = kCTI_NetworkNodeType_Router; 1214 } else if (!strcmp(node_type_name, "end-device")) { 1215 network_node_type = kCTI_NetworkNodeType_EndDevice; 1216 } else if (!strcmp(node_type_name, "sleepy-end-device")) { 1217 network_node_type = kCTI_NetworkNodeType_SleepyEndDevice; 1218 } else if (!strcmp(node_type_name, "synchronized-sleepy-end-device")) { 1219 network_node_type = kCTI_NetworkNodeType_SynchronizedSleepyEndDevice; 1220 } else if (!strcmp(node_type_name, "nl-lurker")) { 1221 network_node_type = kCTI_NetworkNodeType_NestLurker; 1222 } else if (!strcmp(node_type_name, "commissioner")) { 1223 network_node_type = kCTI_NetworkNodeType_Commissioner; 1224 } else if (!strcmp(node_type_name, "leader")) { 1225 network_node_type = kCTI_NetworkNodeType_Leader; 1226 } else if (!strcmp(node_type_name, "sleepy-router")) { 1227 network_node_type = kCTI_NetworkNodeType_SleepyRouter; 1228 } 1229 } 1230 } 1231 } 1232 if (callback != NULL) { 1233 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 1234 callback(conn_ref->context, network_node_type, status); 1235 } 1236 } 1237 1238 cti_status_t 1239 cti_get_network_node_type_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 1240 cti_network_node_type_reply_t NONNULL callback, 1241 run_context_t NULLABLE client_queue, const char *file, int line) 1242 { 1243 cti_callback_t app_callback; 1244 app_callback.network_node_type_reply = callback; 1245 cti_status_t errx; 1246 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1247 1248 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1249 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1250 xpc_dictionary_set_string(dict, "method", "PropGet"); 1251 xpc_dictionary_set_string(dict, "property_name", "Network:NodeType"); 1252 1253 errx = setup_for_command(ref, client_queue, "get_network_node_type", "Network:NodeType", NULL, dict, "WpanctlCmd", 1254 context, app_callback, cti_internal_network_node_type_callback, false, file, line); 1255 xpc_release(dict); 1256 1257 return errx; 1258 } 1259 1260 static void 1261 cti_service_finalize(cti_service_t *service) 1262 { 1263 if (service->server != NULL) { 1264 free(service->server); 1265 } 1266 if (service->service != NULL) { 1267 free(service->service); 1268 } 1269 free(service); 1270 } 1271 1272 static void 1273 cti_service_vec_finalize(cti_service_vec_t *services) 1274 { 1275 size_t i; 1276 1277 if (services->services != NULL) { 1278 for (i = 0; i < services->num; i++) { 1279 if (services->services[i] != NULL) { 1280 RELEASE_HERE(services->services[i], cti_service); 1281 } 1282 } 1283 free(services->services); 1284 } 1285 free(services); 1286 } 1287 1288 cti_service_vec_t * 1289 cti_service_vec_create_(size_t num_services, const char *file, int line) 1290 { 1291 cti_service_vec_t *services = calloc(1, sizeof(*services)); 1292 if (services != NULL) { 1293 if (num_services != 0) { 1294 services->services = calloc(num_services, sizeof(cti_service_t *)); 1295 if (services->services == NULL) { 1296 free(services); 1297 return NULL; 1298 } 1299 } 1300 services->num = num_services; 1301 RETAIN(services, cti_service_vec); 1302 } 1303 return services; 1304 } 1305 1306 void 1307 cti_service_vec_release_(cti_service_vec_t *services, const char *file, int line) 1308 { 1309 RELEASE(services, cti_service_vec); 1310 } 1311 1312 cti_service_t * 1313 cti_service_create_(uint64_t enterprise_number, uint16_t rloc16, uint16_t service_type, 1314 uint16_t service_version, uint8_t *service_data, size_t service_length, 1315 uint8_t *server, size_t server_length, uint16_t service_id, int flags, const char *file, int line) 1316 { 1317 cti_service_t *service = calloc(1, sizeof(*service)); 1318 if (service != NULL) { 1319 service->enterprise_number = enterprise_number; 1320 service->service_type = service_type; 1321 service->service_version = service_version; 1322 service->rloc16 = rloc16; 1323 service->service = service_data; 1324 service->service_length = service_length; 1325 service->server = server; 1326 service->server_length = server_length; 1327 service->service_id = service_id; 1328 service->flags = flags; 1329 RETAIN(service, cti_service); 1330 } 1331 return service; 1332 } 1333 1334 void 1335 cti_service_release_(cti_service_t *service, const char *file, int line) 1336 { 1337 RELEASE(service, cti_service); 1338 } 1339 1340 static uint8_t * 1341 cti_array_to_bytes(object_t array, size_t *length_ret, const char *log_name) 1342 { 1343 size_t length = xpc_array_get_count(array); 1344 size_t i; 1345 uint8_t *ret; 1346 1347 ret = malloc(length); 1348 if (ret == NULL) { 1349 ERROR(PUB_S_SRP ": no memory for return buffer", log_name); 1350 return NULL; 1351 } 1352 1353 for (i = 0; i < length; i++) { 1354 uint64_t v = xpc_array_get_uint64(array, i); 1355 ret[i] = v; 1356 } 1357 *length_ret = length; 1358 return ret; 1359 } 1360 1361 static cti_status_t 1362 cti_parse_services_array(cti_service_vec_t **services, object_t services_array) 1363 { 1364 size_t services_array_length = xpc_array_get_count(services_array); 1365 size_t i, j; 1366 cti_service_vec_t *service_vec; 1367 cti_service_t *service; 1368 cti_status_t status = kCTIStatus_NoError; 1369 1370 service_vec = cti_service_vec_create(services_array_length); 1371 if (service_vec == NULL) { 1372 return kCTIStatus_NoMemory; 1373 } 1374 1375 // Array of arrays 1376 for (i = 0; i < services_array_length; i++) { 1377 object_t service_array = xpc_array_get_value(services_array, i); 1378 int match_count = 0; 1379 bool matched_enterprisenum = false; 1380 bool matched_origin = false; 1381 bool matched_rloc16 = false; 1382 bool matched_serverdata = false; 1383 bool matched_servicedata = false; 1384 bool matched_stable = false; 1385 bool matched_service_id = false; 1386 uint64_t enterprise_number = 0; 1387 uint16_t rloc16 = 0; 1388 uint8_t *server_data = NULL; 1389 size_t server_data_length = 0; 1390 uint8_t *service_data = NULL; 1391 size_t service_data_length = 0; 1392 int flags = 0; 1393 uint16_t service_id = 0; 1394 1395 if (service_array == NULL) { 1396 ERROR("Unable to get service array %zd", i); 1397 } else { 1398 size_t service_array_length = xpc_array_get_count(service_array); 1399 for (j = 0; j < service_array_length; j++) { 1400 object_t *array_sub_dict = xpc_array_get_value(service_array, j); 1401 if (array_sub_dict == NULL) { 1402 ERROR("can't get service_array %zd subdictionary %zd", i, j); 1403 goto service_array_element_failed; 1404 } else { 1405 const char *key = xpc_dictionary_get_string(array_sub_dict, "key"); 1406 if (key == NULL) { 1407 ERROR("Invalid services array %zd subdictionary %zd: no key", i, j); 1408 goto service_array_element_failed; 1409 } else if (!strcmp(key, "EnterpriseNumber")) { 1410 if (matched_enterprisenum) { 1411 ERROR("services array %zd: Enterprise number appears twice.", i); 1412 goto service_array_element_failed; 1413 } 1414 enterprise_number = xpc_dictionary_get_uint64(array_sub_dict, "value"); 1415 matched_enterprisenum = true; 1416 } else if (!strcmp(key, "Origin")) { 1417 if (matched_origin) { 1418 ERROR("Services array %zd: Origin appears twice.", i); 1419 goto service_array_element_failed; 1420 } 1421 const char *origin_string = xpc_dictionary_get_string(array_sub_dict, "value"); 1422 if (origin_string == NULL) { 1423 ERROR("Unable to get origin string from services array %zd", i); 1424 goto service_array_element_failed; 1425 } else if (!strcmp(origin_string, "user")) { 1426 // Not NCP 1427 } else if (!strcmp(origin_string, "ncp")) { 1428 flags |= kCTIFlag_NCP; 1429 } else { 1430 ERROR("unknown origin " PUB_S_SRP, origin_string); 1431 goto service_array_element_failed; 1432 } 1433 matched_origin = true; 1434 } else if (!strcmp(key, "ServerData")) { 1435 if (matched_serverdata) { 1436 ERROR("Services array %zd: Server data appears twice.", i); 1437 goto service_array_element_failed; 1438 } 1439 server_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"), 1440 &server_data_length, "Server data"); 1441 if (server_data == NULL) { 1442 goto service_array_element_failed; 1443 } 1444 matched_serverdata = true; 1445 } else if (!strcmp(key, "ServiceData")) { 1446 if (matched_servicedata) { 1447 ERROR("Services array %zd: Service data appears twice.", i); 1448 goto service_array_element_failed; 1449 } 1450 service_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"), 1451 &service_data_length, "Service data"); 1452 if (service_data == NULL) { 1453 goto service_array_element_failed; 1454 } 1455 matched_servicedata = true; 1456 } else if (!strcmp(key, "Stable")) { 1457 if (matched_stable) { 1458 ERROR("Services array %zd: Stable state appears twice.", i); 1459 goto service_array_element_failed; 1460 } 1461 if (xpc_dictionary_get_bool(array_sub_dict, "value")) { 1462 flags |= kCTIFlag_Stable; 1463 } 1464 matched_stable = true; 1465 } else if (!strcmp(key, "RLOC16")) { 1466 if (matched_rloc16) { 1467 ERROR("Services array %zd: Stable state appears twice.", i); 1468 goto service_array_element_failed; 1469 } 1470 rloc16 = (uint16_t)xpc_dictionary_get_uint64(array_sub_dict, "value"); 1471 matched_rloc16 = true; 1472 } else if (!strcmp(key, "ServiceId")) { 1473 if (matched_service_id) { 1474 ERROR("Services array %zd: Stable state appears twice.", i); 1475 goto service_array_element_failed; 1476 } 1477 service_id = (uint16_t)xpc_dictionary_get_int64(array_sub_dict, "value"); 1478 matched_service_id = true; 1479 } else { 1480 INFO("Unknown key in service array %zd subdictionary %zd: " PUB_S_SRP, i, j, key); 1481 // Not a failure, but don't count it. 1482 continue; 1483 } 1484 match_count++; 1485 } 1486 } 1487 if (match_count != 7) { 1488 ERROR("expecting %d sub-dictionaries to service array %zd, but got %d.", 1489 7, i, match_count); 1490 // No service ID is not fatal (yet), but if some other data is missing, that /is/ fatal. 1491 if (match_count < 6 || (match_count == 6 && matched_service_id)) { 1492 goto service_array_element_failed; 1493 } 1494 } 1495 uint16_t service_type, service_version; 1496 if (enterprise_number == THREAD_ENTERPRISE_NUMBER) { 1497 // two-byte service data for anycast service while on-byte for unicast 1498 // and pref-id 1499 if (service_data_length != 1 && service_data_length != 2) { 1500 INFO("Invalid service data: length = %zd", service_data_length); 1501 goto service_array_element_failed; 1502 } 1503 service_type = service_data[0]; 1504 service_version = 1; 1505 } else { 1506 // We don't support any other enterprise numbers. 1507 service_type = service_version = 0; 1508 } 1509 1510 service = cti_service_create(enterprise_number, rloc16, service_type, service_version, 1511 service_data, service_data_length, server_data, 1512 server_data_length, service_id, flags); 1513 if (service == NULL) { 1514 ERROR("Unable to store service %lld %d %d: out of memory.", enterprise_number, 1515 service_type, service_version); 1516 } else { 1517 service_data = NULL; 1518 server_data = NULL; 1519 service_vec->services[i] = service; 1520 } 1521 goto done_with_service_array; 1522 service_array_element_failed: 1523 if (status == kCTIStatus_NoError) { 1524 status = kCTIStatus_UnknownError; 1525 } 1526 done_with_service_array: 1527 if (server_data != NULL) { 1528 free(server_data); 1529 } 1530 if (service_data != NULL) { 1531 free(service_data); 1532 } 1533 } 1534 } 1535 if (status == kCTIStatus_NoError) { 1536 *services = service_vec; 1537 } else { 1538 if (service_vec != NULL) { 1539 RELEASE_HERE(service_vec, cti_service_vec); 1540 } 1541 } 1542 return status; 1543 } 1544 1545 static void 1546 cti_internal_service_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 1547 { 1548 cti_service_reply_t callback = conn_ref->callback.service_reply; 1549 cti_service_vec_t *vec = NULL; 1550 cti_status_t status = status_in; 1551 if (status == kCTIStatus_NoError) { 1552 object_t result_dictionary = NULL; 1553 status = cti_event_or_response_extract(reply, &result_dictionary); 1554 if (status == kCTIStatus_NoError) { 1555 object_t *value = xpc_dictionary_get_array(result_dictionary, "value"); 1556 if (value == NULL) { 1557 INFO("[CX%d] services array not present in Thread:Services event.", conn_ref->serial); 1558 } else { 1559 status = cti_parse_services_array(&vec, value); 1560 } 1561 } 1562 } 1563 if (callback != NULL) { 1564 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 1565 callback(conn_ref->context, vec, status); 1566 } 1567 if (vec != NULL) { 1568 RELEASE_HERE(vec, cti_service_vec); 1569 } 1570 } 1571 1572 cti_status_t 1573 cti_get_service_list_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 1574 cti_service_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 1575 const char *file, int line) 1576 { 1577 cti_callback_t app_callback; 1578 app_callback.service_reply = callback; 1579 cti_status_t errx; 1580 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1581 1582 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1583 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1584 xpc_dictionary_set_string(dict, "method", "PropGet"); 1585 xpc_dictionary_set_string(dict, "property_name", "Thread:Services"); 1586 1587 errx = setup_for_command(ref, client_queue, "get_service_list", "Thread:Services", NULL, dict, "WpanctlCmd", 1588 context, app_callback, cti_internal_service_reply_callback, false, file, line); 1589 xpc_release(dict); 1590 1591 return errx; 1592 } 1593 1594 static void 1595 cti_prefix_finalize(cti_prefix_t *prefix) 1596 { 1597 free(prefix); 1598 } 1599 1600 static void 1601 cti_prefix_vec_finalize(cti_prefix_vec_t *prefixes) 1602 { 1603 size_t i; 1604 1605 if (prefixes->prefixes != NULL) { 1606 for (i = 0; i < prefixes->num; i++) { 1607 if (prefixes->prefixes[i] != NULL) { 1608 RELEASE_HERE(prefixes->prefixes[i], cti_prefix); 1609 } 1610 } 1611 free(prefixes->prefixes); 1612 } 1613 free(prefixes); 1614 } 1615 1616 cti_prefix_vec_t * 1617 cti_prefix_vec_create_(size_t num_prefixes, const char *file, int line) 1618 { 1619 cti_prefix_vec_t *prefixes = calloc(1, sizeof(*prefixes)); 1620 if (prefixes != NULL) { 1621 if (num_prefixes != 0) { 1622 prefixes->prefixes = calloc(num_prefixes, sizeof(cti_prefix_t *)); 1623 if (prefixes->prefixes == NULL) { 1624 free(prefixes); 1625 return NULL; 1626 } 1627 } 1628 prefixes->num = num_prefixes; 1629 RETAIN(prefixes, cti_prefix_vec); 1630 } 1631 return prefixes; 1632 } 1633 1634 void 1635 cti_prefix_vec_release_(cti_prefix_vec_t *prefixes, const char *file, int line) 1636 { 1637 RELEASE(prefixes, cti_prefix_vec); 1638 } 1639 1640 cti_prefix_t * 1641 cti_prefix_create_(struct in6_addr *prefix, int prefix_length, int metric, int flags, int rloc, bool stable, bool ncp, 1642 const char *file, int line) 1643 { 1644 cti_prefix_t *prefix_ret = calloc(1, sizeof(*prefix_ret)); 1645 if (prefix != NULL) { 1646 prefix_ret->prefix = *prefix; 1647 prefix_ret->prefix_length = prefix_length; 1648 prefix_ret->metric = metric; 1649 prefix_ret->flags = flags; 1650 prefix_ret->rloc = rloc; 1651 prefix_ret->stable = stable; 1652 prefix_ret->ncp = ncp; 1653 RETAIN(prefix_ret, cti_prefix); 1654 } 1655 return prefix_ret; 1656 } 1657 1658 void 1659 cti_prefix_release_(cti_prefix_t *prefix, const char *file, int line) 1660 { 1661 RELEASE(prefix, cti_prefix); 1662 } 1663 1664 static cti_status_t 1665 cti_parse_prefixes_array(cti_prefix_vec_t **vec_ret, object_t prefixes_array) 1666 { 1667 size_t prefixes_array_length = xpc_array_get_count(prefixes_array); 1668 size_t i, j; 1669 cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefixes_array_length); 1670 cti_status_t status = kCTIStatus_NoError; 1671 1672 if (prefixes == NULL) { 1673 INFO("no memory."); 1674 status = kCTIStatus_NoMemory; 1675 } else { 1676 // Array of arrays 1677 for (i = 0; i < prefixes_array_length; i++) { 1678 object_t prefix_array = xpc_array_get_value(prefixes_array, i); 1679 int match_count = 0; 1680 bool matched_address = false; 1681 bool matched_metric = false; 1682 const char *destination = NULL; 1683 int metric = 0; 1684 struct in6_addr prefix_addr; 1685 1686 if (prefix_array == NULL) { 1687 ERROR("Unable to get prefix array %zu", i); 1688 } else { 1689 size_t prefix_array_length = xpc_array_get_count(prefix_array); 1690 for (j = 0; j < prefix_array_length; j++) { 1691 object_t *array_sub_dict = xpc_array_get_value(prefix_array, j); 1692 if (array_sub_dict == NULL) { 1693 ERROR("can't get prefix_array %zu subdictionary %zu", i, j); 1694 goto done_with_prefix_array; 1695 } else { 1696 const char *key = xpc_dictionary_get_string(array_sub_dict, "key"); 1697 if (key == NULL) { 1698 ERROR("Invalid prefixes array %zu subdictionary %zu: no key", i, j); 1699 goto done_with_prefix_array; 1700 } 1701 // Fix me: when <rdar://problem/59371674> is fixed, remove Addreess key test. 1702 else if (!strcmp(key, "Addreess") || !strcmp(key, "Address")) { 1703 if (matched_address) { 1704 ERROR("prefixes array %zu: Address appears twice.", i); 1705 goto done_with_prefix_array; 1706 } 1707 destination = xpc_dictionary_get_string(array_sub_dict, "value"); 1708 if (destination == NULL) { 1709 INFO("null address"); 1710 goto done_with_prefix_array; 1711 } 1712 matched_address = true; 1713 } else if (!strcmp(key, "Metric")) { 1714 if (matched_metric) { 1715 ERROR("prefixes array %zu: Metric appears twice.", i); 1716 goto done_with_prefix_array; 1717 } 1718 metric = (int)xpc_dictionary_get_uint64(array_sub_dict, "value"); 1719 matched_metric = true; 1720 } else { 1721 ERROR("Unknown key in prefix array %zu subdictionary %zu: " PUB_S_SRP, i, j, key); 1722 goto done_with_prefix_array; 1723 } 1724 match_count++; 1725 } 1726 } 1727 if (match_count != 2) { 1728 ERROR("expecting %d sub-dictionaries to prefix array %zu, but got %d.", 1729 2, i, match_count); 1730 goto done_with_prefix_array; 1731 } 1732 1733 // The prefix is in IPv6 address presentation form, so convert it to bits. 1734 char prefix_buffer[INET6_ADDRSTRLEN]; 1735 const char *slash = strchr(destination, '/'); 1736 size_t prefix_pres_len = slash - destination; 1737 if (prefix_pres_len >= INET6_ADDRSTRLEN - 1) { 1738 ERROR("prefixes array %zu: destination is longer than maximum IPv6 address string: " PUB_S_SRP, 1739 j, destination); 1740 goto done_with_prefix_array; 1741 } 1742 #ifndef __clang_analyzer__ // destination is never null at this point 1743 memcpy(prefix_buffer, destination, prefix_pres_len); 1744 #endif 1745 prefix_buffer[prefix_pres_len] = 0; 1746 inet_pton(AF_INET6, prefix_buffer, &prefix_addr); 1747 1748 // Also convert the prefix. 1749 char *endptr = NULL; 1750 int prefix_len = (int)strtol(slash + 1, &endptr, 10); 1751 if (endptr == slash + 1 || *endptr != 0 || prefix_len != 64) { 1752 INFO("bogus prefix length provided by thread: " PUB_S_SRP, destination); 1753 prefix_len = 64; 1754 } 1755 1756 cti_prefix_t *prefix = cti_prefix_create(&prefix_addr, prefix_len, metric, 0, 0, false, false); 1757 if (prefix != NULL) { 1758 prefixes->prefixes[i] = prefix; 1759 } 1760 continue; 1761 done_with_prefix_array: 1762 status = kCTIStatus_UnknownError; 1763 } 1764 } 1765 } 1766 if (status == kCTIStatus_NoError) { 1767 *vec_ret = prefixes; 1768 } else { 1769 if (prefixes != NULL) { 1770 RELEASE_HERE(prefixes, cti_prefix_vec); 1771 } 1772 } 1773 return status; 1774 } 1775 1776 static void 1777 cti_internal_prefix_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 1778 { 1779 cti_prefix_reply_t callback = conn_ref->callback.prefix_reply; 1780 cti_status_t status = status_in; 1781 cti_prefix_vec_t *vec = NULL; 1782 object_t result_dictionary = NULL; 1783 if (status == kCTIStatus_NoError) { 1784 status = cti_event_or_response_extract(reply, &result_dictionary); 1785 if (status == kCTIStatus_NoError) { 1786 object_t *value = xpc_dictionary_get_array(result_dictionary, "value"); 1787 if (value == NULL) { 1788 INFO("prefixes array not present in IPv6:Routes event."); 1789 } else { 1790 status = cti_parse_prefixes_array(&vec, value); 1791 } 1792 } 1793 } 1794 if (callback != NULL) { 1795 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 1796 callback(conn_ref->context, vec, status); 1797 } else { 1798 INFO("[CX%d] Not calling callback.", conn_ref->serial); 1799 } 1800 if (vec != NULL) { 1801 RELEASE_HERE(vec, cti_prefix_vec); 1802 } 1803 } 1804 1805 cti_status_t 1806 cti_get_prefix_list_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 1807 cti_prefix_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 1808 const char *file, int line) 1809 { 1810 cti_callback_t app_callback; 1811 app_callback.prefix_reply = callback; 1812 cti_status_t errx; 1813 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 1814 1815 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 1816 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 1817 xpc_dictionary_set_string(dict, "method", "PropGet"); 1818 xpc_dictionary_set_string(dict, "property_name", "IPv6:Routes"); 1819 1820 errx = setup_for_command(ref, client_queue, "get_prefix_list", "IPv6:Routes", NULL, dict, "WpanctlCmd", 1821 context, app_callback, cti_internal_prefix_reply_callback, false, file, line); 1822 xpc_release(dict); 1823 1824 return errx; 1825 } 1826 1827 static void 1828 cti_route_finalize(cti_route_t *route) 1829 { 1830 free(route); 1831 } 1832 1833 static void 1834 cti_route_vec_finalize(cti_route_vec_t *routes) 1835 { 1836 size_t i; 1837 1838 if (routes->routes != NULL) { 1839 for (i = 0; i < routes->num; i++) { 1840 if (routes->routes[i] != NULL) { 1841 RELEASE_HERE(routes->routes[i], cti_route); 1842 } 1843 } 1844 free(routes->routes); 1845 } 1846 free(routes); 1847 } 1848 1849 cti_route_vec_t * 1850 cti_route_vec_create_(size_t num_routes, const char *file, int line) 1851 { 1852 cti_route_vec_t *routes = calloc(1, sizeof(*routes)); 1853 if (routes != NULL) { 1854 if (num_routes != 0) { 1855 routes->routes = calloc(num_routes, sizeof(cti_route_t *)); 1856 if (routes->routes == NULL) { 1857 free(routes); 1858 return NULL; 1859 } 1860 } 1861 routes->num = num_routes; 1862 RETAIN(routes, cti_route_vec); 1863 } 1864 return routes; 1865 } 1866 1867 void 1868 cti_route_vec_release_(cti_route_vec_t *routes, const char *file, int line) 1869 { 1870 RELEASE(routes, cti_route_vec); 1871 } 1872 1873 cti_route_t * 1874 cti_route_create_(struct in6_addr *prefix, int prefix_length, offmesh_route_origin_t origin, 1875 bool nat64, bool stable, offmesh_route_preference_t preference, int rloc, 1876 bool next_hop_is_host, const char *file, int line) 1877 { 1878 cti_route_t *route_ret = calloc(1, sizeof(*route_ret)); 1879 if (prefix != NULL) { 1880 route_ret->prefix = *prefix; 1881 route_ret->prefix_length = prefix_length; 1882 route_ret->origin = origin; 1883 route_ret->nat64 = nat64; 1884 route_ret->stable = stable; 1885 route_ret->preference = preference; 1886 route_ret->rloc = rloc; 1887 route_ret->next_hop_is_host = next_hop_is_host; 1888 RETAIN(route_ret, cti_route); 1889 } 1890 return route_ret; 1891 } 1892 1893 static cti_status_t 1894 cti_parse_offmesh_routes_array(cti_route_vec_t **vec_ret, object_t routes_array) 1895 { 1896 size_t i, j, routes_array_length = xpc_array_get_count(routes_array); 1897 cti_route_vec_t *routes = cti_route_vec_create(routes_array_length); 1898 cti_status_t status = kCTIStatus_NoError; 1899 1900 if (routes == NULL) { 1901 INFO("no memory."); 1902 status = kCTIStatus_NoMemory; 1903 } else { 1904 // Array of arrays 1905 for (i = 0; i < routes_array_length; i++) { 1906 object_t route_array = xpc_array_get_value(routes_array, i); 1907 if (route_array == NULL) { 1908 ERROR("Unable to get route array %zu", i); 1909 } else { 1910 bool nat64 = false, stable = false, next_hop_is_host = false; 1911 int rloc = 0, prefix_len = 0; 1912 offmesh_route_preference_t pref = offmesh_route_preference_low; 1913 offmesh_route_origin_t origin = offmesh_route_origin_user; 1914 struct in6_addr prefix_addr = { }; 1915 size_t route_array_length = xpc_array_get_count(route_array); 1916 for (j = 0; j < route_array_length; j++) { 1917 object_t *array_sub_dict = xpc_array_get_value(route_array, j); 1918 if (array_sub_dict == NULL) { 1919 ERROR("can't get routes_array %zu subdictionary %zu", i, j); 1920 goto done_with_route_array; 1921 } 1922 const char *key = xpc_dictionary_get_string(array_sub_dict, "key"); 1923 if (key == NULL) { 1924 ERROR("Invalid routes array %zu subdictionary %zu: no key", i, j); 1925 goto done_with_route_array; 1926 } else if (!strcmp(key, "nat64")) { 1927 if (xpc_dictionary_get_uint64(array_sub_dict, "value") == 1) { 1928 nat64 = true; 1929 } 1930 } else if (!strcmp(key, "origin")) { 1931 const char *origin_str = xpc_dictionary_get_string(array_sub_dict, "value"); 1932 if (origin_str == NULL) { 1933 ERROR("Invalid routes array %zu subdictionary %zu: NULL origin", i, j); 1934 goto done_with_route_array; 1935 } 1936 if (!strcmp(origin_str, "ncp")) { 1937 origin = offmesh_route_origin_ncp; 1938 } 1939 } else if (!strcmp(key, "stable")) { 1940 if (xpc_dictionary_get_uint64(array_sub_dict, "value") == 1) { 1941 stable = true; 1942 } 1943 } else if (!strcmp(key, "nextHopIsHost")) { 1944 if (xpc_dictionary_get_bool(array_sub_dict, "value") == true) { 1945 next_hop_is_host = true; 1946 } 1947 } else if (!strcmp(key, "rloc")) { 1948 rloc = (int)xpc_dictionary_get_uint64(array_sub_dict, "value"); 1949 } else if (!strcmp(key, "preference")) { 1950 const char *pref_str = xpc_dictionary_get_string(array_sub_dict, "value"); 1951 if (pref_str == NULL) { 1952 ERROR("Invalid routes array %zu subdictionary %zu: NULL preference", i, j); 1953 goto done_with_route_array; 1954 } 1955 if (!strcmp(pref_str, "high")) { 1956 pref = offmesh_route_preference_high; 1957 } else if (!strcmp(pref_str, "medium")) { 1958 pref = offmesh_route_preference_medium; 1959 } 1960 } else if (!strcmp(key, "address")) { 1961 const char *addr_str = xpc_dictionary_get_string(array_sub_dict, "value"); 1962 if (addr_str == NULL) { 1963 ERROR("Invalid routes array %zu subdictionary %zu: NULL address", i, j); 1964 goto done_with_route_array; 1965 } 1966 // The prefix is in IPv6 address presentation form, so convert it to bits. 1967 char prefix_buffer[INET6_ADDRSTRLEN]; 1968 const char *slash = strchr(addr_str, '/'); 1969 if (slash == NULL) { 1970 ERROR("bad address " PRI_S_SRP, addr_str); 1971 goto done_with_route_array; 1972 } 1973 size_t prefix_pres_len = slash - addr_str; 1974 if (prefix_pres_len > INET6_ADDRSTRLEN - 1) { 1975 ERROR("routes array %zu: destination is longer than maximum IPv6 address string: " PRI_S_SRP, 1976 i, addr_str); 1977 goto done_with_route_array; 1978 } 1979 #ifndef __clang_analyzer__ // destination is never null at this point 1980 memcpy(prefix_buffer, addr_str, prefix_pres_len); 1981 #endif 1982 prefix_buffer[prefix_pres_len] = 0; 1983 if (inet_pton(AF_INET6, prefix_buffer, &prefix_addr) != 1) { 1984 ERROR("invalid ipv6 address " PRI_S_SRP, prefix_buffer); 1985 goto done_with_route_array; 1986 } 1987 // Convert the prefix. 1988 char *endptr = NULL; 1989 long tmp = strtol(slash + 1, &endptr, 10); 1990 if (endptr == slash + 1 || *endptr != 0 || tmp < 0 || tmp > 128) { 1991 ERROR("bogus ipv6 prefix length provided by thread: " PRI_S_SRP, addr_str); 1992 goto done_with_route_array; 1993 } else { 1994 prefix_len = (int)tmp; 1995 } 1996 } else { 1997 ERROR("Invalid routes array %zu subdictionary %zu: unknown key " PUB_S_SRP, i, j, key); 1998 goto done_with_route_array; 1999 } 2000 } 2001 cti_route_t *r = cti_route_create(&prefix_addr, prefix_len, origin, nat64, stable, pref, rloc, next_hop_is_host); 2002 if (r == NULL) { 2003 ERROR("No memory when create route."); 2004 goto done_with_route_array; 2005 } else { 2006 SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_addr.s6_addr, prefix_buf); 2007 INFO("Got offmesh route " PRI_SEGMENTED_IPv6_ADDR_SRP " len %d origin:" PUB_S_SRP " nat64:" 2008 PUB_S_SRP " stable:" PUB_S_SRP " preference:%d rloc:0x%04x next_hop_is_host:" PUB_S_SRP, 2009 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_addr.s6_addr, prefix_buf), prefix_len, 2010 origin == offmesh_route_origin_user ? "user":"ncp", nat64 ? "yes" : "no", 2011 stable ? "yes" : "no", pref, rloc, next_hop_is_host ? "yes" : "no"); 2012 routes->routes[i] = r; 2013 } 2014 continue; 2015 done_with_route_array: 2016 status = kCTIStatus_UnknownError; 2017 break; 2018 } 2019 } 2020 } 2021 if (status == kCTIStatus_NoError) { 2022 *vec_ret = routes; 2023 } else { 2024 if (routes != NULL) { 2025 RELEASE_HERE(routes, cti_route_vec); 2026 } 2027 } 2028 return status; 2029 } 2030 2031 static void 2032 cti_internal_offmesh_route_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 2033 { 2034 cti_offmesh_route_reply_t callback = conn_ref->callback.offmesh_route_reply; 2035 cti_status_t status = status_in; 2036 cti_route_vec_t *vec = NULL; 2037 object_t result_dictionary = NULL; 2038 if (status == kCTIStatus_NoError) { 2039 status = cti_event_or_response_extract(reply, &result_dictionary); 2040 if (status == kCTIStatus_NoError) { 2041 object_t *value = xpc_dictionary_get_array(result_dictionary, "value"); 2042 if (value == NULL) { 2043 INFO("offmesh route array not present in Thread:OffMeshroutes event."); 2044 } else { 2045 status = cti_parse_offmesh_routes_array(&vec, value); 2046 } 2047 } 2048 } 2049 if (callback != NULL) { 2050 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 2051 callback(conn_ref->context, vec, status); 2052 } else { 2053 INFO("[CX%d] Not calling callback for %p", conn_ref->serial, conn_ref); 2054 } 2055 if (vec != NULL) { 2056 RELEASE_HERE(vec, cti_route_vec); 2057 } 2058 } 2059 2060 cti_status_t 2061 cti_get_offmesh_route_list_(srp_server_t *UNUSED server, cti_connection_t *ref, 2062 void *NULLABLE context, cti_offmesh_route_reply_t NONNULL callback, 2063 run_context_t NULLABLE client_queue, const char *file, int line) 2064 { 2065 cti_callback_t app_callback; 2066 app_callback.offmesh_route_reply = callback; 2067 cti_status_t errx; 2068 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2069 2070 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2071 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2072 xpc_dictionary_set_string(dict, "method", "PropGet"); 2073 xpc_dictionary_set_string(dict, "property_name", "Thread:OffMeshroutes"); 2074 2075 errx = setup_for_command(ref, client_queue, "get_offmesh_route_list", "Thread:OffMeshroutes", NULL, dict, "WpanctlCmd", 2076 context, app_callback, cti_internal_offmesh_route_reply_callback, false, file, line); 2077 INFO("get_offmesh_route_list result %d", errx); 2078 xpc_release(dict); 2079 2080 return errx; 2081 } 2082 2083 static cti_status_t 2084 cti_parse_onmesh_prefix_array(cti_prefix_vec_t **vec_ret, object_t prefix_array) 2085 { 2086 size_t i, prefix_array_length = xpc_array_get_count(prefix_array); 2087 cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefix_array_length); 2088 cti_status_t status = kCTIStatus_NoError; 2089 2090 if (prefixes == NULL) { 2091 INFO("no memory."); 2092 status = kCTIStatus_NoMemory; 2093 } else { 2094 for (i = 0; i < prefix_array_length; i++) { 2095 const char *route = xpc_array_get_string(prefix_array, i); 2096 bool stable = false; 2097 int rloc = 0, prefix_len = 0, flags = 0; 2098 bool ncp = false; 2099 struct in6_addr prefix_addr = { }; 2100 2101 char *dup = strdup(route); 2102 if (dup == NULL) { 2103 INFO("no memory when strdup"); 2104 goto done_with_prefix_array; 2105 } 2106 char *token, *remain = dup; 2107 int index = 0; 2108 while ((token = strtok_r(remain, " ", &remain))) { 2109 if (index == 0) { 2110 if (!inet_pton(AF_INET6, token, &prefix_addr)) { 2111 ERROR("invalid ipv6 address " PUB_S_SRP, token); 2112 goto done_with_prefix_array; 2113 } 2114 } else if (index == 1) { 2115 if (strncmp(token, "prefix_len:", 11)) { 2116 ERROR("expecting prefix_len rather than " PRI_S_SRP " at %d", token, index); 2117 goto done_with_prefix_array; 2118 } 2119 char *endptr = NULL; 2120 prefix_len = (int)strtol(token + 11, &endptr, 10); 2121 } else if (index == 2) { 2122 if (strncmp(token, "origin:", 7)) { 2123 ERROR("expecting origin rather than " PRI_S_SRP " at %d", token, index); 2124 goto done_with_prefix_array; 2125 } 2126 if (!strcmp(token + 7, "ncp")){ 2127 ncp = true; 2128 } else if (strcmp(token + 7, "user")) { 2129 ERROR("unexpected origin: " PRI_S_SRP, token + 7); 2130 goto done_with_prefix_array; 2131 } 2132 } else if (index == 3) { 2133 if (strncmp(token, "stable:", 7)) { 2134 ERROR("expecting table rather than " PRI_S_SRP " at %d", token, index); 2135 goto done_with_prefix_array; 2136 } 2137 if (!strcmp(token + 7, "yes")){ 2138 stable = true; 2139 } else if (strcmp(token + 7, "no")) { 2140 ERROR("unexpected boolean state: " PRI_S_SRP, token + 7); 2141 goto done_with_prefix_array; 2142 } 2143 } else if (index == 4) { 2144 if (strncmp(token, "flags:", 6)) { 2145 ERROR("expecting flags rather than " PRI_S_SRP " at %d", token, index); 2146 goto done_with_prefix_array; 2147 } 2148 char *endptr = NULL; 2149 flags = ntohs((int)strtol(token + 6, &endptr, 16)); 2150 } else if (index == 14) { 2151 if (strncmp(token, "rloc:", 5)) { 2152 ERROR("expecting rloc rather than " PRI_S_SRP " at %d", token, index); 2153 goto done_with_prefix_array; 2154 } 2155 char *endptr = NULL; 2156 rloc = (int)strtol(token + 5, &endptr, 16); 2157 } else { 2158 // Anything else is in the parsed flags, which we don't care about, or is a bogon, which will probably result 2159 // in a failure elsewhere. We're not really expecting bogons, so putting in extra code here to flag them would 2160 // be useless. 2161 } 2162 index++; 2163 } 2164 SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_addr.s6_addr, prefix_buf); 2165 INFO("got prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " len %d origin:" PUB_S_SRP " stable:" PUB_S_SRP " flags:%x rloc:%04x", 2166 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_addr.s6_addr, prefix_buf), prefix_len, 2167 ncp ? "ncp" : "user", stable ? "yes" : "no", flags, rloc); 2168 cti_prefix_t *r = cti_prefix_create(&prefix_addr, prefix_len, 0, flags, rloc, stable, ncp); 2169 if (r) { 2170 prefixes->prefixes[i] = r; 2171 } else { 2172 ERROR("no memory for parsed prefix!"); 2173 goto done_with_prefix_array; 2174 } 2175 free(dup); 2176 continue; 2177 done_with_prefix_array: 2178 status = kCTIStatus_UnknownError; 2179 free(dup); 2180 break; 2181 } 2182 } 2183 if (status == kCTIStatus_NoError) { 2184 *vec_ret = prefixes; 2185 } else { 2186 if (prefixes != NULL) { 2187 RELEASE_HERE(prefixes, cti_prefix_vec); 2188 } 2189 } 2190 return status; 2191 } 2192 2193 static void 2194 cti_internal_onmesh_prefix_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 2195 { 2196 cti_onmesh_prefix_reply_t callback = conn_ref->callback.onmesh_prefix_reply; 2197 cti_status_t status = status_in; 2198 cti_prefix_vec_t *vec = NULL; 2199 object_t result_dictionary = NULL; 2200 if (status == kCTIStatus_NoError) { 2201 status = cti_event_or_response_extract(reply, &result_dictionary); 2202 if (status == kCTIStatus_NoError) { 2203 object_t *value = xpc_dictionary_get_array(result_dictionary, "value"); 2204 if (value == NULL) { 2205 INFO("onmesh prefix array not present in Thread:OnMeshPrefixes event."); 2206 } else { 2207 status = cti_parse_onmesh_prefix_array(&vec, value); 2208 } 2209 } 2210 } 2211 if (callback != NULL) { 2212 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 2213 callback(conn_ref->context, vec, status); 2214 } else { 2215 INFO("[CX%d] not calling callback for %p", conn_ref->serial, conn_ref); 2216 } 2217 if (vec != NULL) { 2218 RELEASE_HERE(vec, cti_prefix_vec); 2219 } 2220 } 2221 2222 cti_status_t 2223 cti_get_onmesh_prefix_list_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref, 2224 void *NULLABLE context, cti_onmesh_prefix_reply_t NONNULL callback, 2225 run_context_t NULLABLE client_queue, const char *file, int line) 2226 { 2227 cti_callback_t app_callback; 2228 app_callback.onmesh_prefix_reply = callback; 2229 cti_status_t errx; 2230 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2231 2232 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2233 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2234 xpc_dictionary_set_string(dict, "method", "PropGet"); 2235 xpc_dictionary_set_string(dict, "property_name", "Thread:OnMeshPrefixes"); 2236 2237 errx = setup_for_command(ref, client_queue, "get_onmesh_prefix_list", "Thread:OnMeshPrefixes", NULL, dict, 2238 "WpanctlCmd", context, app_callback, cti_internal_onmesh_prefix_reply_callback, false, 2239 file, line); 2240 INFO("result %d", errx); 2241 xpc_release(dict); 2242 2243 return errx; 2244 } 2245 2246 static void 2247 cti_internal_rloc16_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 2248 { 2249 cti_rloc16_reply_t callback = conn_ref->callback.rloc16_reply; 2250 cti_status_t status = status_in; 2251 object_t result_dictionary = NULL; 2252 uint16_t rloc16 = 0; 2253 if (status == kCTIStatus_NoError) { 2254 status = cti_event_or_response_extract(reply, &result_dictionary); 2255 // The reply format is 2256 // commandData: {response: "> e000 2257 // Done", method: "OtCtlCmd"} 2258 // where e000 is the rloc16 that we want to extract 2259 if (status == kCTIStatus_NoError) { 2260 const char *rloc16_str = xpc_dictionary_get_string(result_dictionary, "response"); 2261 char *endptr = "\n"; 2262 rloc16 = (uint16_t)strtol(&rloc16_str[2], &endptr, 16); 2263 } 2264 } 2265 if (callback != NULL) { 2266 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 2267 callback(conn_ref->context, rloc16, status); 2268 } else { 2269 INFO("[CX%d] not calling callback for %p", conn_ref->serial, conn_ref); 2270 } 2271 } 2272 2273 cti_status_t 2274 cti_get_rloc16_(srp_server_t *UNUSED server, cti_connection_t *ref, 2275 void *NULLABLE context, cti_rloc16_reply_t NONNULL callback, 2276 run_context_t NULLABLE client_queue, const char *file, int line) 2277 { 2278 cti_callback_t app_callback; 2279 app_callback.rloc16_reply = callback; 2280 cti_status_t errx; 2281 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2282 2283 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2284 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2285 xpc_dictionary_set_string(dict, "method", "OtCtlCmd"); 2286 xpc_dictionary_set_string(dict, "otctl_cmd", "rloc16"); 2287 2288 errx = setup_for_command(ref, client_queue, "get_rloc16", NULL, NULL, dict, "WpanctlCmd", 2289 context, app_callback, cti_internal_rloc16_reply_callback, false, file, line); 2290 INFO("get_rloc16 result %d", errx); 2291 xpc_release(dict); 2292 2293 return errx; 2294 } 2295 2296 static void 2297 cti_internal_wed_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in) 2298 { 2299 cti_wed_reply_t callback = conn_ref->callback.wed_reply; 2300 cti_service_vec_t *vec = NULL; 2301 cti_status_t status = status_in; 2302 2303 const char *extended_mac = NULL; 2304 const char *ml_eid = NULL; 2305 bool added = false; 2306 2307 if (status == kCTIStatus_NoError) { 2308 object_t result_dictionary = NULL; 2309 status = cti_event_or_response_extract(reply, &result_dictionary); 2310 if (status == kCTIStatus_NoError) { 2311 object_t *value_array = xpc_dictionary_get_array(result_dictionary, "value"); 2312 if (value_array == NULL) { 2313 INFO("[CX%d] wed status array not present in wed status event.", conn_ref->serial); 2314 goto out; 2315 } else { 2316 size_t value_array_length = xpc_array_get_count(value_array); 2317 for (size_t i = 0; i < value_array_length; i++) { 2318 object_t *elt = xpc_array_get_value(value_array, i); 2319 xpc_type_t type = xpc_get_type(elt); 2320 if (type != XPC_TYPE_DICTIONARY) { 2321 ERROR("non-dictionary element of value array"); 2322 goto out; 2323 } 2324 const char *key = xpc_dictionary_get_string(elt, "key"); 2325 if (key == NULL) { 2326 ERROR("no key in value array"); 2327 goto out; 2328 } 2329 const char *value = xpc_dictionary_get_string(elt, "value"); 2330 if (!strcmp(key, "extendedMACAddress")) { 2331 extended_mac = value; 2332 } else if (!strcmp(key, "mleid")) { 2333 ml_eid = value; 2334 } else if (!strcmp(key, "status")) { 2335 if (!strcmp(value, "wed_added")) { 2336 added = true; 2337 } else if (!strcmp(value, "wed_removed")) { 2338 added = false; 2339 } else { 2340 ERROR("unknown wed status " PUB_S_SRP, value); 2341 goto out; 2342 } 2343 } else { 2344 if (value == NULL) { 2345 INFO("unknown key in response: " PUB_S_SRP, key); 2346 } else { 2347 INFO("unknown key " PUB_S_SRP " with value " PRI_S_SRP, key, value); 2348 } 2349 } 2350 } 2351 } 2352 } 2353 } 2354 out: 2355 if (callback != NULL) { 2356 if (added && (ml_eid == NULL || extended_mac == NULL)) { 2357 added = false; 2358 } 2359 INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref); 2360 callback(conn_ref->context, extended_mac, ml_eid, added, status); 2361 } 2362 if (vec != NULL) { 2363 RELEASE_HERE(vec, cti_service_vec); 2364 } 2365 } 2366 2367 cti_status_t 2368 cti_track_wed_status_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 2369 cti_wed_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 2370 const char *file, int line) 2371 { 2372 cti_callback_t app_callback; 2373 app_callback.wed_reply = callback; 2374 cti_status_t errx; 2375 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2376 2377 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2378 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2379 xpc_dictionary_set_string(dict, "method", "PropGet"); 2380 xpc_dictionary_set_string(dict, "property_name", "WakeOnDeviceConnectionStatus"); 2381 2382 errx = setup_for_command(ref, client_queue, "get_wed_status", "WakeOnDeviceConnectionStatus", NULL, dict, "WpanctlCmd", 2383 context, app_callback, cti_internal_wed_reply_callback, false, file, line); 2384 xpc_release(dict); 2385 2386 return errx; 2387 } 2388 2389 cti_status_t 2390 cti_track_neighbor_ml_eid_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context, 2391 cti_string_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 2392 const char *file, int line) 2393 { 2394 cti_callback_t app_callback; 2395 app_callback.string_property_reply = callback; 2396 cti_status_t errx; 2397 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2398 2399 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2400 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2401 xpc_dictionary_set_string(dict, "method", "PropGet"); 2402 xpc_dictionary_set_string(dict, "property_name", "ThreadNeighborMeshLocalAddress"); 2403 2404 errx = setup_for_command(ref, client_queue, "get_neighbor_ml_eid", "ThreadNeighborMeshLocalAddress", "ThreadNeighborMeshLocalAddress", dict, "WpanctlCmd", 2405 context, app_callback, cti_internal_string_event_reply, false, file, line); 2406 xpc_release(dict); 2407 2408 return errx; 2409 } 2410 2411 cti_status_t 2412 cti_add_ml_eid_mapping_(srp_server_t *UNUSED server, void *NULLABLE context, 2413 cti_reply_t NONNULL callback, run_context_t NULLABLE client_queue, 2414 struct in6_addr *omr_addr, struct in6_addr *ml_eid, const char *hostname, 2415 const char *file, int line) 2416 { 2417 cti_callback_t app_callback; 2418 app_callback.reply = callback; 2419 cti_status_t errx; 2420 object_t dict = xpc_dictionary_create(NULL, NULL, 0); 2421 char addrbuf[INET6_ADDRSTRLEN]; 2422 2423 xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1"); 2424 xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2"); 2425 xpc_dictionary_set_string(dict, "method", "UpdateAccessoryData"); 2426 inet_ntop(AF_INET6, omr_addr, addrbuf, sizeof(addrbuf)); 2427 xpc_dictionary_set_string(dict, "ipaddr_add", addrbuf); 2428 inet_ntop(AF_INET6, ml_eid, addrbuf, sizeof(addrbuf)); 2429 xpc_dictionary_set_string(dict, "ipaddr_lookup", addrbuf); 2430 xpc_dictionary_set_string(dict, "host_info", hostname); 2431 2432 errx = setup_for_command(NULL, client_queue, "add_mle_mapping", NULL, NULL, dict, "WpanctlCmd", 2433 context, app_callback, cti_internal_reply_callback, false, file, line); 2434 xpc_release(dict); 2435 2436 return errx; 2437 } 2438 2439 2440 cti_status_t 2441 cti_events_discontinue(cti_connection_t ref) 2442 { 2443 if (ref->connection != NULL) { 2444 INFO("[CX%d] canceling connection %p", ref->serial, ref->connection); 2445 xpc_connection_cancel(ref->connection); 2446 } 2447 // This is releasing the caller's reference. 2448 cti_connection_release(ref); 2449 return kCTIStatus_NoError; 2450 } 2451 2452 // Local Variables: 2453 // mode: C 2454 // tab-width: 4 2455 // c-file-style: "bsd" 2456 // c-basic-offset: 4 2457 // fill-column: 120 2458 // indent-tabs-mode: nil 2459 // End: 2460