2 Copyright (c) 2012 Frank Lahm <franklahm@gmail.com>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
17 #endif /* HAVE_CONFIG_H */
27 #include <atalk/errchk.h>
28 #include <atalk/util.h>
29 #include <atalk/logger.h>
30 #include <atalk/talloc.h>
31 #include <atalk/dalloc.h>
32 #include <atalk/byteorder.h>
33 #include <atalk/netatalk_conf.h>
34 #include <atalk/volume.h>
36 #include "spotlight.h"
38 /**************************************************************************************************
39 * RPC data marshalling and unmarshalling
40 **************************************************************************************************/
42 /* FPSpotlightRPC subcommand codes */
43 #define SPOTLIGHT_CMD_FLAGS 2
44 #define SPOTLIGHT_CMD_RPC 3
45 #define SPOTLIGHT_CMD_VOLPATH 4
47 /* Spotlight epoch is UNIX epoch minus SPOTLIGHT_TIME_DELTA */
48 #define SPOTLIGHT_TIME_DELTA INT64_C(280878921600U)
50 #define SQ_TYPE_NULL 0x0000
51 #define SQ_TYPE_COMPLEX 0x0200
52 #define SQ_TYPE_INT64 0x8400
53 #define SQ_TYPE_BOOL 0x0100
54 #define SQ_TYPE_FLOAT 0x8500
55 #define SQ_TYPE_DATA 0x0700
56 #define SQ_TYPE_CNIDS 0x8700
57 #define SQ_TYPE_UUID 0x0e00
58 #define SQ_TYPE_DATE 0x8600
60 #define SQ_CPX_TYPE_ARRAY 0x0a00
61 #define SQ_CPX_TYPE_STRING 0x0c00
62 #define SQ_CPX_TYPE_UTF16_STRING 0x1c00
63 #define SQ_CPX_TYPE_DICT 0x0d00
64 #define SQ_CPX_TYPE_CNIDS 0x1a00
65 #define SQ_CPX_TYPE_FILEMETA 0x1b00
67 #define SUBQ_SAFETY_LIM 20
69 /* Can be ored and used as flags */
70 #define SL_ENC_LITTLE_ENDIAN 1
71 #define SL_ENC_BIG_ENDIAN 2
72 #define SL_ENC_UTF_16 4
74 static uint64_t spotlight_ntoh64(const char *buf, int encoding)
76 if (encoding == SL_ENC_LITTLE_ENDIAN)
79 return ntoh64(LVAL(buf, 0));
84 spotlight_ntohieee_double(tvbuff_t *tvb, gint offset, guint encoding)
86 if (encoding == ENC_LITTLE_ENDIAN)
87 return tvb_get_letohieee_double(tvb, offset);
89 return tvb_get_ntohieee_double(tvb, offset);
93 * Returns the UTF-16 string encoding, by checking the 2-byte byte order mark.
94 * If there is no byte order mark, -1 is returned.
97 spotlight_get_utf16_string_encoding(tvbuff_t *tvb, gint offset, gint query_length, guint encoding) {
100 /* check for byte order mark */
101 utf16_encoding = ENC_BIG_ENDIAN;
102 if (query_length >= 2) {
103 guint16 byte_order_mark;
104 if (encoding == ENC_LITTLE_ENDIAN)
105 byte_order_mark = tvb_get_letohs(tvb, offset);
107 byte_order_mark = tvb_get_ntohs(tvb, offset);
109 if (byte_order_mark == 0xFFFE) {
110 utf16_encoding = ENC_BIG_ENDIAN | ENC_UTF_16;
112 else if (byte_order_mark == 0xFEFF) {
113 utf16_encoding = ENC_LITTLE_ENDIAN | ENC_UTF_16;
117 return utf16_encoding;
121 spotlight_int64(tvbuff_t *tvb, proto_tree *tree, gint offset, guint encoding)
124 guint64 query_data64;
126 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
127 count = query_data64 >> 32;
131 while (i++ < count) {
132 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
133 proto_tree_add_text(tree, tvb, offset, 8, "int64: 0x%016" G_GINT64_MODIFIER "x", query_data64);
141 spotlight_date(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset, guint encoding)
144 guint64 query_data64;
147 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
148 count = query_data64 >> 32;
151 if (count > SUBQ_SAFETY_LIM) {
152 expert_add_info_format(pinfo, tree, PI_MALFORMED, PI_ERROR,
153 "Subquery count (%d) > safety limit (%d)", count, SUBQ_SAFETY_LIM);
158 while (i++ < count) {
159 query_data64 = spotlight_ntoh64(tvb, offset, encoding) >> 24;
160 t.secs = query_data64 - SPOTLIGHT_TIME_DELTA;
162 proto_tree_add_time(tree, hf_afp_spotlight_date, tvb, offset, 8, &t);
170 spotlight_uuid(tvbuff_t *tvb, proto_tree *tree, gint offset, guint encoding)
173 guint64 query_data64;
175 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
176 count = query_data64 >> 32;
180 while (i++ < count) {
181 proto_tree_add_item(tree, hf_afp_spotlight_uuid, tvb, offset, 16, ENC_BIG_ENDIAN);
189 spotlight_float(tvbuff_t *tvb, proto_tree *tree, gint offset, guint encoding)
192 guint64 query_data64;
195 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
196 count = query_data64 >> 32;
200 while (i++ < count) {
201 fval = spotlight_ntohieee_double(tvb, offset, encoding);
202 proto_tree_add_text(tree, tvb, offset, 8, "float: %f", fval);
210 spotlight_CNID_array(tvbuff_t *tvb, proto_tree *tree, gint offset, guint encoding)
213 guint64 query_data64;
217 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
218 count = query_data64 & 0xffff;
219 unknown1 = (query_data64 & 0xffff0000) >> 16;
220 unknown2 = query_data64 >> 32;
222 proto_tree_add_text(tree, tvb, offset + 2, 2, "unknown1: 0x%04" G_GINT16_MODIFIER "x",
224 proto_tree_add_text(tree, tvb, offset + 4, 4, "unknown2: 0x%08" G_GINT32_MODIFIER "x",
230 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
231 proto_tree_add_text(tree, tvb, offset, 8, "CNID: %" G_GINT64_MODIFIER "u",
239 static const char *spotlight_get_qtype_string(guint64 query_type)
241 switch (query_type) {
244 case SQ_TYPE_COMPLEX:
261 static const char *spotlight_get_cpx_qtype_string(guint64 cpx_query_type)
263 switch (cpx_query_type) {
264 case SQ_CPX_TYPE_ARRAY:
266 case SQ_CPX_TYPE_STRING:
268 case SQ_CPX_TYPE_UTF16_STRING:
269 return "utf-16 string";
270 case SQ_CPX_TYPE_DICT:
272 case SQ_CPX_TYPE_CNIDS:
274 case SQ_CPX_TYPE_FILEMETA:
282 spotlight_dissect_query_loop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset,
283 guint64 cpx_query_type, gint count, gint toc_offset, guint encoding)
288 guint64 query_data64;
291 guint64 complex_query_type;
292 guint unicode_encoding;
295 proto_item *item_query;
296 proto_tree *sub_tree;
299 * This loops through a possibly nested query data structure.
300 * The outermost one is always without count and called from
301 * dissect_spotlight() with count = INT_MAX thus the while (...)
302 * loop terminates if (offset >= toc_offset).
303 * If nested structures are found, these will have an encoded element
304 * count which is used in a recursive call to
305 * spotlight_dissect_query_loop as count parameter, thus in this case
306 * the while (...) loop will terminate when count reaches 0.
308 while ((offset < (toc_offset - 8)) && (count > 0)) {
309 query_data64 = spotlight_ntoh64(tvb, offset, encoding);
310 query_length = (query_data64 & 0xffff) * 8;
311 if (query_length == 0) {
312 /* XXX - report this as an error */
315 query_type = (query_data64 & 0xffff0000) >> 16;
317 switch (query_type) {
318 case SQ_TYPE_COMPLEX:
319 toc_index = (gint)((query_data64 >> 32) - 1);
320 query_data64 = spotlight_ntoh64(tvb, toc_offset + toc_index * 8, encoding);
321 complex_query_type = (query_data64 & 0xffff0000) >> 16;
323 switch (complex_query_type) {
324 case SQ_CPX_TYPE_ARRAY:
325 case SQ_CPX_TYPE_DICT:
326 subquery_count = (gint)(query_data64 >> 32);
327 item_query = proto_tree_add_text(tree, tvb, offset, query_length,
328 "%s, toc index: %u, children: %u",
329 spotlight_get_cpx_qtype_string(complex_query_type),
333 case SQ_CPX_TYPE_STRING:
335 query_data64 = spotlight_ntoh64(tvb, offset + 8, encoding);
336 query_length = (query_data64 & 0xffff) * 8;
337 item_query = proto_tree_add_text(tree, tvb, offset, query_length + 8,
338 "%s, toc index: %u, string: '%s'",
339 spotlight_get_cpx_qtype_string(complex_query_type),
341 tvb_get_ephemeral_string(tvb, offset + 16, query_length - 8));
343 case SQ_CPX_TYPE_UTF16_STRING:
345 * This is an UTF-16 string.
346 * Dissections show the typical byte order mark 0xFFFE or 0xFEFF, respectively.
347 * However the existence of such a mark can not be assumed.
348 * If the mark is missing, big endian encoding is assumed.
352 query_data64 = spotlight_ntoh64(tvb, offset + 8, encoding);
353 query_length = (query_data64 & 0xffff) * 8;
355 unicode_encoding = spotlight_get_utf16_string_encoding(tvb, offset + 16, query_length - 8, encoding);
356 mark_exists = (unicode_encoding & ENC_UTF_16);
357 unicode_encoding &= ~ENC_UTF_16;
359 item_query = proto_tree_add_text(tree, tvb, offset, query_length + 8,
360 "%s, toc index: %u, utf-16 string: '%s'",
361 spotlight_get_cpx_qtype_string(complex_query_type),
363 tvb_get_ephemeral_unicode_string(tvb, offset + (mark_exists ? 18 : 16),
364 query_length - (mark_exists? 10 : 8), unicode_encoding));
368 item_query = proto_tree_add_text(tree, tvb, offset, query_length,
369 "type: %s (%s), toc index: %u, children: %u",
370 spotlight_get_qtype_string(query_type),
371 spotlight_get_cpx_qtype_string(complex_query_type),
377 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
379 offset = spotlight_dissect_query_loop(tvb, pinfo, sub_tree, offset, complex_query_type, subquery_count, toc_offset, encoding);
383 subquery_count = (gint)(query_data64 >> 32);
384 if (subquery_count > count) {
385 item_query = proto_tree_add_text(tree, tvb, offset, query_length, "null");
386 expert_add_info_format(pinfo, item_query, PI_MALFORMED, PI_ERROR,
387 "Subquery count (%d) > query count (%d)", subquery_count, count);
389 } else if (subquery_count > 20) {
390 item_query = proto_tree_add_text(tree, tvb, offset, query_length, "null");
391 expert_add_info_format(pinfo, item_query, PI_PROTOCOL, PI_WARN,
392 "Abnormal number of subqueries (%d)", subquery_count);
393 count -= subquery_count;
395 for (i = 0; i < subquery_count; i++, count--)
396 proto_tree_add_text(tree, tvb, offset, query_length, "null");
398 offset += query_length;
401 proto_tree_add_text(tree, tvb, offset, query_length, "bool: %s",
402 (query_data64 >> 32) ? "true" : "false");
404 offset += query_length;
407 item_query = proto_tree_add_text(tree, tvb, offset, 8, "int64");
408 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
409 j = spotlight_int64(tvb, sub_tree, offset, encoding);
411 offset += query_length;
414 item_query = proto_tree_add_text(tree, tvb, offset, 8, "UUID");
415 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
416 j = spotlight_uuid(tvb, sub_tree, offset, encoding);
418 offset += query_length;
421 item_query = proto_tree_add_text(tree, tvb, offset, 8, "float");
422 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
423 j = spotlight_float(tvb, sub_tree, offset, encoding);
425 offset += query_length;
428 switch (cpx_query_type) {
429 case SQ_CPX_TYPE_STRING:
430 proto_tree_add_text(tree, tvb, offset, query_length, "string: '%s'",
431 tvb_get_ephemeral_string(tvb, offset + 8, query_length - 8));
433 case SQ_CPX_TYPE_UTF16_STRING: {
434 /* description see above */
435 unicode_encoding = spotlight_get_utf16_string_encoding(tvb, offset + 8, query_length, encoding);
436 mark_exists = (unicode_encoding & ENC_UTF_16);
437 unicode_encoding &= ~ENC_UTF_16;
439 proto_tree_add_text(tree, tvb, offset, query_length, "utf-16 string: '%s'",
440 tvb_get_ephemeral_unicode_string(tvb, offset + (mark_exists ? 10 : 8),
441 query_length - (mark_exists? 10 : 8), unicode_encoding));
444 case SQ_CPX_TYPE_FILEMETA:
445 if (query_length <= 8) {
446 /* item_query = */ proto_tree_add_text(tree, tvb, offset, query_length, "filemeta (empty)");
448 item_query = proto_tree_add_text(tree, tvb, offset, query_length, "filemeta");
449 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
450 (void)dissect_spotlight(tvb, pinfo, sub_tree, offset + 8);
455 offset += query_length;
458 if (query_length <= 8) {
459 /* item_query = */ proto_tree_add_text(tree, tvb, offset, query_length, "CNID Array (empty)");
461 item_query = proto_tree_add_text(tree, tvb, offset, query_length, "CNID Array");
462 sub_tree = proto_item_add_subtree(item_query, ett_afp_spotlight_query_line);
463 spotlight_CNID_array(tvb, sub_tree, offset + 8, encoding);
466 offset += query_length;
469 if ((j = spotlight_date(tvb, pinfo, tree, offset, encoding)) == -1)
472 offset += query_length;
475 proto_tree_add_text(tree, tvb, offset, query_length, "type: %s",
476 spotlight_get_qtype_string(query_type));
478 offset += query_length;
487 dissect_spotlight(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gint offset)
496 proto_item *item_queries_data;
497 proto_tree *sub_tree_queries;
498 proto_item *item_toc;
499 proto_tree *sub_tree_toc;
501 if (strncmp(tvb_get_ephemeral_string(tvb, offset, 8), "md031234", 8) == 0)
502 encoding = ENC_BIG_ENDIAN;
504 encoding = ENC_LITTLE_ENDIAN;
505 proto_tree_add_text(tree,
510 encoding == ENC_BIG_ENDIAN ?
511 "Big Endian" : "Litte Endian");
514 toc_offset = (spotlight_ntoh64(tvb, offset, encoding) >> 32) * 8;
515 if (toc_offset < 8) {
516 proto_tree_add_text(tree,
520 "ToC Offset: %" G_GINT64_MODIFIER "u < 8 (bogus)",
525 if (offset + toc_offset + 8 > G_MAXINT) {
526 proto_tree_add_text(tree,
530 "ToC Offset: %" G_GINT64_MODIFIER "u > %u (bogus)",
532 G_MAXINT - 8 - offset);
535 querylen = (spotlight_ntoh64(tvb, offset, encoding) & 0xffffffff) * 8;
537 proto_tree_add_text(tree,
541 "ToC Offset: %" G_GINT64_MODIFIER "u Bytes, Query length: %" G_GINT64_MODIFIER "u < 8 (bogus)",
547 if (querylen > G_MAXINT) {
548 proto_tree_add_text(tree,
552 "ToC Offset: %" G_GINT64_MODIFIER "u Bytes, Query length: %" G_GINT64_MODIFIER "u > %u (bogus)",
558 proto_tree_add_text(tree,
562 "ToC Offset: %" G_GINT64_MODIFIER "u Bytes, Query length: %" G_GINT64_MODIFIER "u Bytes",
567 toc_entries = (gint)(spotlight_ntoh64(tvb, offset + (gint)toc_offset, encoding) & 0xffff);
569 item_queries_data = proto_tree_add_text(tree,
573 "Spotlight RPC data");
574 sub_tree_queries = proto_item_add_subtree(item_queries_data, ett_afp_spotlight_queries);
577 offset = spotlight_dissect_query_loop(tvb, pinfo, sub_tree_queries, offset, SQ_CPX_TYPE_ARRAY, INT_MAX, offset + (gint)toc_offset + 8, encoding);
580 if (toc_entries < 1) {
581 proto_tree_add_text(tree,
584 (gint)querylen - (gint)toc_offset,
585 "Complex types ToC (%u < 1 - bogus)",
590 item_toc = proto_tree_add_text(tree,
593 (gint)querylen - (gint)toc_offset,
594 "Complex types ToC (%u entries)",
596 sub_tree_toc = proto_item_add_subtree(item_toc, ett_afp_spotlight_toc);
597 proto_tree_add_text(sub_tree_toc, tvb, offset, 2, "Number of entries (%u)", toc_entries);
598 proto_tree_add_text(sub_tree_toc, tvb, offset + 2, 2, "unknown");
599 proto_tree_add_text(sub_tree_toc, tvb, offset + 4, 4, "unknown");
602 for (i = 0; i < toc_entries; i++, offset += 8) {
603 toc_entry = spotlight_ntoh64(tvb, offset, encoding);
604 if ((((toc_entry & 0xffff0000) >> 16) == SQ_CPX_TYPE_ARRAY)
605 || (((toc_entry & 0xffff0000) >> 16) == SQ_CPX_TYPE_DICT)) {
606 proto_tree_add_text(sub_tree_toc,
610 "%u: count: %" G_GINT64_MODIFIER "u, type: %s, offset: %" G_GINT64_MODIFIER "u",
613 spotlight_get_cpx_qtype_string((toc_entry & 0xffff0000) >> 16),
614 (toc_entry & 0xffff) * 8);
615 } else if ((((toc_entry & 0xffff0000) >> 16) == SQ_CPX_TYPE_STRING)
616 || (((toc_entry & 0xffff0000) >> 16) == SQ_CPX_TYPE_UTF16_STRING)) {
617 proto_tree_add_text(sub_tree_toc,
621 "%u: pad byte count: %" G_GINT64_MODIFIER "x, type: %s, offset: %" G_GINT64_MODIFIER "u",
623 8 - (toc_entry >> 32),
624 spotlight_get_cpx_qtype_string((toc_entry & 0xffff0000) >> 16),
625 (toc_entry & 0xffff) * 8);
628 proto_tree_add_text(sub_tree_toc,
632 "%u: unknown: 0x%08" G_GINT64_MODIFIER "x, type: %s, offset: %" G_GINT64_MODIFIER "u",
635 spotlight_get_cpx_qtype_string((toc_entry & 0xffff0000) >> 16),
636 (toc_entry & 0xffff) * 8);
646 static DALLOC_CTX *unpack_spotlight(TALLOC_CTX *mem_ctx, char *ibuf, size_t ibuflen)
652 EC_NULL_LOG( query = talloc_zero(mem_ctx, DALLOC_CTX) );
666 /**************************************************************************************************
668 **************************************************************************************************/
669 int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
672 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
675 int endianess = SL_ENC_LITTLE_ENDIAN;
684 LOG(logtype_default, log_note, "afp_spotlight_rpc(vid: %" PRIu16 ")", vid);
686 if ((vol = getvolbyvid(vid)) == NULL) {
687 LOG(logtype_default, log_error, "afp_spotlight_rpc: bad volume id: %" PRIu16 ")", vid);
692 /* IVAL(ibuf, 2): unknown, always 0x00008004, some flags ? */
694 cmd = RIVAL(ibuf, 6);
695 LOG(logtype_default, log_note, "afp_spotlight_rpc(cmd: %d)", cmd);
697 /* IVAL(ibuf, 10: unknown, always 0x00000000 */
701 case SPOTLIGHT_CMD_VOLPATH: {
702 RSIVAL(rbuf, 0, ntohs(vid));
704 int len = strlen(vol->v_path) + 1;
705 strncpy(rbuf + 8, vol->v_path, len);
709 case SPOTLIGHT_CMD_FLAGS:
712 case SPOTLIGHT_CMD_RPC:
713 /* IVAL(buf, 14): our reply in SPOTLIGHT_CMD_FLAGS */
714 /* IVAL(buf, 18): length */
715 /* IVAL(buf, 22): endianess, ignored, we assume little endian */
720 talloc_free(tmp_ctx);
727 /**************************************************************************************************
729 **************************************************************************************************/
731 #ifdef SPOT_TEST_MAIN
733 static const char *neststrings[] = {
743 static int dd_dump(DALLOC_CTX *dd, int nestinglevel)
747 printf("%sArray(#%d): {\n", neststrings[nestinglevel], talloc_array_length(dd->dd_talloc_array));
749 for (int n = 0; n < talloc_array_length(dd->dd_talloc_array); n++) {
751 type = talloc_get_name(dd->dd_talloc_array[n]);
753 if (STRCMP(type, ==, "int64_t")) {
755 memcpy(&i, dd->dd_talloc_array[n], sizeof(int64_t));
756 printf("%s%d:\t%" PRId64 "\n", neststrings[nestinglevel + 1], n, i);
757 } else if (STRCMP(type, ==, "uint32_t")) {
759 memcpy(&i, dd->dd_talloc_array[n], sizeof(uint32_t));
760 printf("%s%d:\t%" PRIu32 "\n", neststrings[nestinglevel + 1], n, i);
761 } else if (STRCMP(type, ==, "char *")) {
763 memcpy(&s, dd->dd_talloc_array[n], sizeof(char *));
764 printf("%s%d:\t%s\n", neststrings[nestinglevel + 1], n, s);
765 } else if (STRCMP(type, ==, "_Bool")) {
767 memcpy(&bl, dd->dd_talloc_array[n], sizeof(bool));
768 printf("%s%d:\t%s\n", neststrings[nestinglevel + 1], n, bl ? "true" : "false");
769 } else if (STRCMP(type, ==, "dd_t")) {
771 memcpy(&nested, dd->dd_talloc_array[n], sizeof(DALLOC_CTX *));
772 dd_dump(nested, nestinglevel + 1);
773 } else if (STRCMP(type, ==, "cnid_array_t")) {
775 memcpy(&cnids, dd->dd_talloc_array[n], sizeof(cnid_array_t *));
776 printf("%s%d:\tunkn1: %" PRIu16 ", unkn2: %" PRIu32,
777 neststrings[nestinglevel + 1], n, cnids->ca_unkn1, cnids->ca_unkn2);
779 dd_dump(cnids->ca_cnids, nestinglevel + 1);
782 printf("%s}\n", neststrings[nestinglevel]);
787 int main(int argc, char **argv)
789 TALLOC_CTX *mem_ctx = talloc_new(NULL);
790 DALLOC_CTX *dd = talloc_zero(mem_ctx, DALLOC_CTX);
793 set_processname("spot");
794 setuplog("default:info", "/dev/tty");
796 LOG(logtype_default, log_info, "Start");
799 dalloc_add(dd, &i, int64_t);
802 dalloc_add(dd, &i, int64_t);
805 char *str = talloc_strdup(dd, "hello world");
806 dalloc_add(dd, &str, char *);
809 dalloc_add(dd, &b, bool);
812 dalloc_add(dd, &b, bool);
815 /* add a nested array */
816 DALLOC_CTX *nested = talloc_zero(dd, DALLOC_CTX);
818 dalloc_add(nested, &i, int64_t);
819 dalloc_add(dd, &nested, DALLOC_CTX);
821 /* test a CNID array */
823 cnid_array_t *cnids = talloc_zero(dd, cnid_array_t);
825 cnids->ca_cnids = talloc_zero(cnids, DALLOC_CTX);
830 dalloc_add(cnids->ca_cnids, &id, uint32_t);
831 dalloc_add(dd, &cnids, cnid_array_t);
835 talloc_free(mem_ctx);