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