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