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