Coverage Report

Created: 2025-12-14 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/dict_unknown.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** Deal with 'unknown' attributes, creating ephemeral dictionary attributes for them
18
 *
19
 * @file src/lib/util/dict_unknown.c
20
 *
21
 * @copyright 2019 The FreeRADIUS server project
22
 */
23
RCSID("$Id: 98351278da1b0e109a207f44c98197c0845c19a8 $")
24
25
#include <freeradius-devel/util/dict_priv.h>
26
27
static int dict_attr_unknown_init(fr_dict_attr_t const *parent, UNUSED fr_dict_attr_t const *da, fr_type_t type, fr_dict_attr_flags_t *flags)
28
6.04M
{
29
6.04M
  flags->is_unknown = true;
30
31
6.04M
  if (parent->flags.internal) {
32
0
    fr_strerror_printf("Cannot create 'raw' attribute of data type '%s' which is 'internal'",
33
0
           fr_type_to_str(type));
34
0
    return -1;
35
0
  }
36
37
6.04M
  if ((parent->type == FR_TYPE_UNION) && (type != FR_TYPE_OCTETS)) {
38
0
    fr_strerror_printf("Cannot create 'raw' attribute of data type '%s' which has parent data type 'union'",
39
0
           fr_type_to_str(type));
40
0
    return -1;
41
0
  }
42
43
6.04M
  if (parent->depth >= FR_DICT_MAX_TLV_STACK) {
44
39
    fr_strerror_const("Attribute depth is too large");
45
39
    return -1;
46
39
  }
47
48
  /*
49
   *  If we are leveraging an existing attribute, then do some additional checks.
50
   */
51
6.04M
  if (da) {
52
2.45M
    if (da->flags.internal) {
53
0
      fr_strerror_printf("Cannot create unknown attribute from internal attribute %s", da->name);
54
0
      return -1;
55
0
    }
56
57
    /*
58
     *  @todo - do we actually care about this?
59
     *
60
     *  If we fix the unknown allocations to always use the raw number as the name, then it
61
     *  should be fine to change the data types.
62
     */
63
2.45M
    if (type != FR_TYPE_OCTETS) {
64
37.5k
      if (da->type != type) {
65
0
        fr_strerror_printf("Cannot allocate unknown attribute (%s) which changes data type from '%s' to '%s'",
66
0
             da->name,
67
0
               fr_type_to_str(da->type),
68
0
               fr_type_to_str(type));
69
0
        return -1;
70
0
      }
71
37.5k
    }
72
2.45M
  }
73
74
  /*
75
   *  Ensure that raw members of a structure have the correct length.
76
   */
77
6.04M
  if (parent->type == FR_TYPE_STRUCT) {
78
22.9k
    if (!da) {
79
4
      fr_strerror_printf("Cannot create 'raw' attribute of data type '%s' which has parent data type 'struct'",
80
4
             fr_type_to_str(type));
81
4
      return -1;
82
4
    }
83
84
22.9k
    if (fr_type_is_leaf(da->type)) {
85
19.0k
      if (fr_type_is_structural(type)) goto cannot_change_type;
86
87
19.0k
      fr_assert(da->flags.is_known_width);
88
89
19.0k
      flags->is_known_width = true;
90
19.0k
      flags->length = da->flags.length;
91
92
19.0k
    } else if (da->type != type) {
93
3.90k
    cannot_change_type:
94
      /*
95
       *  @todo - why not?  So long as the size is the same...
96
       */
97
3.90k
      fr_strerror_printf("Cannot create 'raw' attribute in 'struct' which changes data type from '%s' to '%s'",
98
3.90k
             fr_type_to_str(da->type), fr_type_to_str(type));
99
3.90k
      return -1;
100
3.90k
    }
101
22.9k
  }
102
103
6.04M
  return 0;
104
6.04M
}
105
106
/** Converts an unknown to a known by adding it to the internal dictionaries.
107
 *
108
 * Does not free old #fr_dict_attr_t, that is left up to the caller.
109
 *
110
 * @param[in] dict    of protocol context we're operating in.
111
 *        If NULL the internal dictionary will be used.
112
 * @param[in] unknown   attribute to add.
113
 * @return
114
 *  - Existing #fr_dict_attr_t if unknown was found in a dictionary.
115
 *  - A new entry representing unknown.
116
 */
117
fr_dict_attr_t const *fr_dict_attr_unknown_add(fr_dict_t *dict, fr_dict_attr_t const *unknown)
118
0
{
119
0
  fr_dict_attr_t const *da;
120
0
  fr_dict_attr_t const *parent;
121
0
  fr_dict_attr_flags_t flags;
122
123
0
  if (unlikely(dict->read_only)) {
124
0
    fr_strerror_printf("%s dictionary has been marked as read only", fr_dict_root(dict)->name);
125
0
    return NULL;
126
0
  }
127
128
#ifdef STATIC_ANALYZER
129
  if (!unknown->name || !unknown->parent) return NULL;
130
#endif
131
132
0
  da = fr_dict_attr_by_name(NULL, unknown->parent, unknown->name);
133
0
  if (da) {
134
0
    if (da->attr == unknown->attr) return da;
135
136
0
    fr_strerror_printf("Unknown attribute '%s' conflicts with existing attribute in namespace '%s'",
137
0
           da->name, unknown->parent->name);
138
0
    return da;
139
0
  }
140
141
  /*
142
   *  Define the complete unknown hierarchy
143
   */
144
0
  if (unknown->parent && unknown->parent->flags.is_unknown) {
145
0
    parent = fr_dict_attr_unknown_add(dict, unknown->parent);
146
0
    if (!parent) {
147
0
      fr_strerror_printf_push("Failed adding parent \"%s\"", unknown->parent->name);
148
0
      return NULL;
149
0
    }
150
0
  } else {
151
0
    parent = unknown->parent;
152
0
  }
153
154
0
  memcpy(&flags, &unknown->flags, sizeof(flags));
155
0
  flags.is_unknown = 0;
156
157
  /*
158
   *  If this is a vendor, we skip most of the sanity
159
   *  checks and add it to the vendor hash, and add it
160
   *  as a child attribute to the Vendor-Specific
161
   *  container.
162
   */
163
0
  if (unknown->type == FR_TYPE_VENDOR) {
164
0
    fr_dict_attr_t *n;
165
166
0
    if (dict_vendor_add(dict, unknown->name, unknown->attr) < 0) return NULL;
167
168
0
    n = dict_attr_alloc(dict->pool, parent, unknown->name, unknown->attr, unknown->type,
169
0
            &(dict_attr_args_t){ .flags = &flags });
170
0
    if (unlikely(!n)) return NULL;
171
172
    /*
173
     *  Setup parenting for the attribute
174
     */
175
0
    if (dict_attr_child_add(UNCONST(fr_dict_attr_t *, unknown->parent), n) < 0) return NULL;
176
177
0
    return n;
178
0
  }
179
180
  /*
181
   *  Look up the attribute by number.  If it doesn't exist,
182
   *  add it both by name and by number.  If it does exist,
183
   *  add it only by name.
184
   */
185
0
  da = fr_dict_attr_child_by_num(parent, unknown->attr);
186
0
  if (da) {
187
0
    fr_dict_attr_t *n;
188
189
0
    n = dict_attr_alloc(dict->pool, parent, unknown->name, unknown->attr, unknown->type,
190
0
            &(dict_attr_args_t){ .flags = &flags });
191
0
    if (!n) return NULL;
192
193
    /*
194
     *  Add the unknown by NAME.  e.g. if the admin does "Attr-26", we want
195
     *  to return "Attr-26", and NOT "Vendor-Specific".  The rest of the server
196
     *  is responsible for converting "Attr-26 = 0x..." to an actual attribute,
197
     *  if it so desires.
198
     */
199
0
    if (dict_attr_add_to_namespace(parent, n) < 0) {
200
0
      talloc_free(n);
201
0
      return NULL;
202
0
    }
203
204
0
    return n;
205
0
  }
206
207
  /*
208
   *  Add the attribute by both name and number.
209
   *
210
   *  Fixme - Copy extensions?
211
   */
212
0
  if (fr_dict_attr_add(dict, parent, unknown->name, unknown->attr, unknown->type, &flags) < 0) return NULL;
213
214
  /*
215
   *  For paranoia, return it by name.
216
   */
217
0
  return fr_dict_attr_by_name(NULL, parent, unknown->name);
218
0
}
219
220
/** Free dynamically allocated (unknown attributes)
221
 *
222
 * If the da was dynamically allocated it will be freed, else the function
223
 * will return without doing anything.
224
 *
225
 * @param[in] da to free.
226
 */
227
void fr_dict_attr_unknown_free(fr_dict_attr_t const **da)
228
157k
{
229
157k
  if (!da || !*da) return;
230
231
  /* Don't free real DAs */
232
157k
  if (!(*da)->flags.is_unknown) {
233
33.2k
    return;
234
33.2k
  }
235
236
123k
  talloc_const_free(*da);
237
238
123k
  *da = NULL;
239
123k
}
240
241
/**  Allocate an unknown DA.
242
 *
243
 */
244
fr_dict_attr_t *fr_dict_attr_unknown_alloc(TALLOC_CTX *ctx, fr_dict_attr_t const *da, fr_type_t type)
245
2.45M
{
246
2.45M
  fr_dict_attr_t    *n;
247
2.45M
  fr_dict_attr_t const  *parent;
248
2.45M
  fr_dict_attr_flags_t  flags = {};
249
250
2.45M
  fr_assert(!da->flags.is_root); /* cannot copy root attributes */
251
2.45M
  fr_assert(da->parent);
252
253
2.45M
  switch (type) {
254
36.1M
  case FR_TYPE_LEAF:
255
36.1M
  case FR_TYPE_GROUP:
256
2.43M
  case FR_TYPE_TLV:
257
2.43M
  case FR_TYPE_VSA:
258
2.45M
  case FR_TYPE_VENDOR:
259
2.45M
    break;
260
261
0
  default:
262
0
    fr_strerror_printf("Invalid data type '%s' for unknown attribute", fr_type_to_str(type));
263
0
    return NULL;
264
2.45M
  }
265
266
2.45M
  switch (da->type) {
267
35.4M
  case FR_TYPE_LEAF:
268
35.4M
  case FR_TYPE_STRUCTURAL:
269
2.45M
    break;
270
271
0
  default:
272
0
    fr_strerror_printf("Cannot create unknown attribute from data type '%s'", fr_type_to_str(da->type));
273
0
    return NULL;
274
2.45M
  }
275
276
2.45M
  if (dict_attr_unknown_init(da->parent, da, type, &flags)) return NULL;
277
278
  /*
279
   *  Set the unknown flags.  Note that we don't copy any other flags, as they are all likely to be wrong.
280
   */
281
2.44M
  flags.is_raw = 1;
282
283
  /*
284
   *  Allocate an attribute.
285
   */
286
2.44M
  n = dict_attr_alloc_null(ctx, da->dict->proto);
287
2.44M
  if (!n) return NULL;
288
289
  /*
290
   *  We want to have parent / child relationships, AND to
291
   *  copy all unknown parents, AND to free the unknown
292
   *  parents when this 'da' is freed.  We therefore talloc
293
   *  the parent from the 'da'.
294
   */
295
2.44M
  if (da->parent->flags.is_unknown) {
296
30.4k
    parent = fr_dict_attr_unknown_copy(n, da->parent);
297
30.4k
    if (!parent) {
298
0
    error:
299
0
      talloc_free(n);
300
0
      return NULL;
301
0
    }
302
303
2.41M
  } else {
304
2.41M
    parent = da->parent;
305
2.41M
  }
306
307
  /*
308
   *  Initialize the rest of the fields.
309
   */
310
2.44M
  if (dict_attr_init(&n, parent, da->name, da->attr, type, &(dict_attr_args_t){ .flags = &flags }) < 0) {
311
0
    goto error;
312
0
  }
313
314
  /*
315
   *  Copy protocol-specific extents, and hope to heck that the protocol encoder knows what it's doing.
316
   */
317
2.44M
  if (fr_dict_attr_ext(da, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC) &&
318
298k
      !dict_attr_ext_copy(&n, da, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC)) {
319
0
    goto error;
320
0
  }
321
322
  /*
323
   *  Do NOT copy extents.  The name and da_stack extents are already defined.  We do NOT copy
324
   *  existing children, references, keys, enumv, etc.  If the unknown attribute is a group, it's
325
   *  ref is already set to the root, or to a copy of the input DA.  If the unknown attribute is a
326
   *  TLV, then it cannot have known children.  If an unknown attribute is a leaf, then it cannot
327
   *  have known enums.
328
   */
329
330
2.44M
  DA_VERIFY(n);
331
332
2.44M
  return n;
333
2.44M
}
334
335
/** Copy a known or unknown attribute to produce an unknown attribute with the specified name
336
 *
337
 * Will copy the complete hierarchy down to the first known attribute.
338
 */
339
fr_dict_attr_t *fr_dict_attr_unknown_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
340
2.33M
{
341
2.33M
  fr_type_t type = da->type;
342
343
  /*
344
   *  VENDOR, etc. are logical containers, and can have
345
   *  unknown children, so they're left alone.  All other
346
   *  base types are mangled to OCTETs.
347
   *
348
   *  Note that we can't allocate an unknown STRUCT.  If the
349
   *  structure is malformed, then it's just a sequence of
350
   *  OCTETS.  Similarly, if a GROUP is malformed, then we
351
   *  have no idea what's inside of it, and we make it OCTETS.
352
   */
353
2.33M
  switch (type) {
354
15.8k
  case FR_TYPE_VENDOR:
355
15.8k
    fr_assert(da->flags.type_size != 0);
356
15.8k
    break;
357
358
21.6k
  case FR_TYPE_TLV:
359
21.6k
  case FR_TYPE_VSA:
360
21.6k
    break;
361
362
2.30M
  default:
363
2.30M
    type = FR_TYPE_OCTETS;
364
2.30M
    break;
365
2.33M
  }
366
367
2.33M
  return fr_dict_attr_unknown_alloc(ctx, da, type);
368
2.33M
}
369
370
/** Initialise a fr_dict_attr_t from a number and a data type
371
 *
372
 * @param[in] ctx   to allocate the attribute in.
373
 * @param[in] parent    of the unknown attribute (may also be unknown).
374
 * @param[in] num   of the unknown attribute.
375
 * @param[in] type    data type
376
 * @param[in] raw   is it raw, i.e. _bad_ value, versus unknown?
377
 * @return
378
 *  - An fr_dict_attr_t on success.
379
 *  - NULL on failure.
380
 */
381
fr_dict_attr_t *fr_dict_attr_unknown_typed_afrom_num_raw(TALLOC_CTX *ctx, fr_dict_attr_t const *parent, unsigned int num, fr_type_t type, bool raw)
382
3.59M
{
383
3.59M
  fr_dict_attr_flags_t  flags = {
384
3.59M
          .is_raw = raw,
385
3.59M
        };
386
3.59M
  fr_dict_attr_t const  *da = NULL;
387
388
3.59M
  if (parent->flags.internal) {
389
33
    fr_strerror_printf("Cannot create 'raw' attribute from internal parent '%s' of data type '%s'",
390
33
           parent->name, fr_type_to_str(parent->type));
391
33
    return NULL;
392
33
  }
393
394
3.59M
  if (((parent->type == FR_TYPE_TLV) || (parent->type == FR_TYPE_VENDOR))) {
395
3.58M
    if ((uint64_t) num >= ((uint64_t) 1 << (8 * parent->flags.type_size))) {
396
89
      fr_strerror_printf("Invalid attribute number '%u' - it must be no more than %u bits in size",
397
89
             num, 8 * parent->flags.type_size);
398
89
      return NULL;
399
89
    }
400
3.58M
  }
401
402
3.59M
  switch (type) {
403
0
  default:
404
0
    fr_strerror_printf("Cannot allocate unknown attribute '%u' - invalid data type '%s'",
405
0
           num, fr_type_to_str(type));
406
0
    return NULL;
407
408
6.84k
  case FR_TYPE_VENDOR:
409
6.84k
    if (parent->type != FR_TYPE_VSA) goto fail;
410
411
6.84k
    if (parent->flags.is_unknown) goto fail;
412
6.84k
    break;
413
414
53.6M
  case FR_TYPE_LEAF:
415
53.6M
  case FR_TYPE_TLV:
416
3.58M
  case FR_TYPE_VSA:
417
3.58M
    if (!fr_type_is_structural_except_vsa(parent->type)) {
418
0
    fail:
419
0
      fr_strerror_printf("Cannot allocate unknown attribute '%u' data type '%s' with parent %s data type '%s'",
420
0
             num, fr_type_to_str(type),
421
0
             parent->name, fr_type_to_str(parent->type));
422
0
      return NULL;
423
0
    }
424
425
    /*
426
     *  We can convert anything to 'octets'.
427
     */
428
3.58M
    if (type == FR_TYPE_OCTETS) break;
429
430
    /*
431
     *  But we shouldn't be able to create a raw attribute which is a _different_ type than an
432
     *  existing one.
433
     */
434
22.6k
    da = fr_dict_attr_child_by_num(parent, num);
435
22.6k
    break;
436
3.59M
  }
437
438
3.59M
  if (dict_attr_unknown_init(parent, da, type, &flags)) return NULL;
439
440
3.59M
  return dict_attr_alloc(ctx, parent, NULL, num, type,
441
3.59M
             &(dict_attr_args_t){ .flags = &flags });
442
3.59M
}
443
444
/** Create a fr_dict_attr_t from an ASCII attribute and value
445
 *
446
 * Where the attribute name is in the form:
447
 *  - %d
448
 *  - %d.%d.%d...
449
 *
450
 * @note If vendor != 0, an unknown vendor (may) also be created, parented by
451
 *  the correct VSA attribute. This is accessible via vp->parent,
452
 *  and will be use the unknown da as its talloc parent.
453
 *
454
 * @param[in] ctx   to alloc new attribute in.
455
 * @param[out] out    Where to write the head of the chain unknown
456
 *        dictionary attributes.
457
 * @param[in] parent    Attribute to use as the root for resolving OIDs in.
458
 *        Usually the root of a protocol dictionary.
459
 * @param[in] in    OID string to parse
460
 * @param[in] type    data type of the unknown attribute
461
 * @return
462
 *  - The number of bytes parsed on success.
463
 *  - <= 0 on failure.  Negative offset indicates parse error position.
464
 */
465
fr_slen_t fr_dict_attr_unknown_afrom_oid_substr(TALLOC_CTX *ctx,
466
             fr_dict_attr_t const **out,
467
                   fr_dict_attr_t const *parent,
468
             fr_sbuff_t *in, fr_type_t type)
469
0
{
470
0
  fr_sbuff_t    our_in = FR_SBUFF(in);
471
0
  fr_dict_attr_t const  *our_parent = parent;
472
0
  fr_dict_attr_t    *n = NULL;
473
0
  fr_dict_attr_flags_t  flags = { .is_raw = true, };
474
475
0
  *out = NULL;
476
477
  /*
478
   *  Allocate the final attribute first, so that any
479
   *  unknown parents can be freed when this da is freed.
480
   *
481
   *      See fr_dict_attr_unknown_afrom_da() for more details.
482
   *
483
   *  Note also that we copy the input name, even if it is
484
   *  not normalized.
485
   *
486
   *  While the name of this attribute is "Attr-#.#.#", one
487
   *  or more of the leading components may, in fact, be
488
   *  known.
489
   */
490
0
  n = dict_attr_alloc_null(ctx, parent->dict->proto);
491
492
  /*
493
   *  Loop until there's no more component separators.
494
   */
495
0
  for (;;) {
496
0
    uint32_t    num;
497
0
    fr_sbuff_parse_error_t  sberr;
498
499
0
    fr_sbuff_out(&sberr, &num, &our_in);
500
0
    switch (sberr) {
501
0
    case FR_SBUFF_PARSE_OK:
502
0
      switch (our_parent->type) {
503
      /*
504
       *  If the parent is a VSA, this component
505
       *  must specify a vendor.
506
       */
507
0
      case FR_TYPE_VSA:
508
0
      {
509
0
        fr_dict_attr_t  *ni;
510
511
0
        if (fr_sbuff_next_if_char(&our_in, '.')) {
512
0
          ni = fr_dict_attr_unknown_vendor_afrom_num(n, our_parent, num);
513
0
          if (!ni) {
514
0
          error:
515
0
            talloc_free(n);
516
0
            FR_SBUFF_ERROR_RETURN(&our_in);
517
0
          }
518
0
          our_parent = ni;
519
0
          continue;
520
0
        }
521
0
        if (dict_attr_init(&n, our_parent, NULL, num, FR_TYPE_VENDOR,
522
0
               &(dict_attr_args_t){ .flags = &flags }) < 0) goto error;
523
0
      }
524
0
        break;
525
526
      /*
527
       *  If it's structural, this component must
528
       *  specify a TLV.
529
       */
530
0
      case FR_TYPE_STRUCTURAL_EXCEPT_VSA:
531
0
      {
532
0
        fr_dict_attr_t  *ni;
533
534
0
        if (fr_sbuff_next_if_char(&our_in, '.')) {
535
0
          ni = fr_dict_attr_unknown_typed_afrom_num(n, our_parent, num, FR_TYPE_TLV);
536
0
          if (!ni) goto error;
537
0
          our_parent = ni;
538
0
          continue;
539
0
        }
540
0
      }
541
0
        FALL_THROUGH;
542
543
0
      default:
544
        /*
545
         *  Leaf type with more components
546
         *  is an error.
547
         */
548
0
        if (fr_sbuff_is_char(&our_in, '.')) {
549
0
          fr_strerror_printf("Interior OID component cannot proceed a %s type",
550
0
                 fr_type_to_str(our_parent->type));
551
0
          goto error;
552
0
        }
553
554
0
        if (dict_attr_unknown_init(our_parent, NULL, type, &flags)) goto error;
555
556
0
        if (dict_attr_init(&n, our_parent, NULL, num, type,
557
0
               &(dict_attr_args_t){ .flags = &flags }) < 0) goto error;
558
0
        break;
559
0
      }
560
0
      break;
561
562
0
    default:
563
0
    {
564
0
      fr_sbuff_marker_t c_start;
565
566
0
      fr_sbuff_marker(&c_start, &our_in);
567
0
      fr_sbuff_adv_past_allowed(&our_in, FR_DICT_ATTR_MAX_NAME_LEN, fr_dict_attr_allowed_chars, NULL);
568
0
      fr_strerror_printf("Unknown attribute \"%.*s\" for parent \"%s\"",
569
0
             (int)fr_sbuff_behind(&c_start), fr_sbuff_current(&c_start), our_parent->name);
570
0
      goto error;
571
0
    }
572
0
    }
573
0
    break;
574
0
  }
575
576
0
  DA_VERIFY(n);
577
578
0
  *out = n;
579
580
0
  FR_SBUFF_SET_RETURN(in, &our_in);
581
0
}
582
583
/** Fixup the parent of an unknown attribute using an equivalent known attribute
584
 *
585
 * This can be useful where an unknown attribute's ancestors are added to
586
 * a dictionary but not the unknown attribute itself.
587
 *
588
 * @param[in] da  to fixup.
589
 * @param[in] parent  to assign.  If NULL, we will attempt to resolve
590
 *      the parent in the dictionary the current unknown
591
 *      attribute extends.
592
 * @return
593
 *  - 0 on success.
594
 *  - -1 on failure.
595
 */
596
int fr_dict_attr_unknown_parent_to_known(fr_dict_attr_t *da, fr_dict_attr_t const *parent)
597
0
{
598
0
  fr_dict_attr_t const *da_u, *da_k;
599
600
0
  if (parent) {
601
    /*
602
     *  Walk back up the hierarchy until we get to a known
603
     *  ancestor on the unknown side.
604
     */
605
0
    for (da_u = da->parent, da_k = parent;
606
0
         da_k && da_u && da_u->flags.is_unknown;
607
0
         da_u = da_u->parent, da_k = da_k->parent) {
608
0
      if (unlikely(da_u->attr != da_k->attr)) {
609
0
        fr_strerror_printf("Unknown parent number %u does not match "
610
0
               "known parent number %u (%s)",
611
0
               da_u->attr, da_k->attr, da_k->name);
612
0
        return -1;
613
0
      }
614
615
0
      if (unlikely(da_u->depth != da_k->depth)) {
616
0
        fr_strerror_printf("Unknown parent depth %u does not match "
617
0
               "known parent depth %u (%s)",
618
0
               da_u->depth, da_k->depth, da_k->name);
619
0
        return -1;
620
0
      }
621
0
    }
622
0
    if ((da_k == NULL) != (da_u == NULL)) {
623
0
      fr_strerror_printf("Truncated or over-extended hierarchy "
624
0
             "for unknown attribute %u", da->attr);
625
0
      return -1;
626
0
    }
627
0
  } else {
628
0
    parent = fr_dict_attr_unknown_resolve(fr_dict_by_da(da), da->parent);
629
0
    if (!parent) {
630
0
      fr_strerror_printf("Failed resolving unknown attribute %u "
631
0
             "in dictionary", da->attr);
632
0
      return -1;
633
0
    }
634
0
  }
635
636
0
  da->parent = fr_dict_attr_unconst(parent);
637
638
0
  return 0;
639
0
}
640
641
/** Check to see if we can convert a nested TLV structure to known attributes
642
 *
643
 * @param[in] dict      to search in.
644
 * @param[in] da      Nested tlv structure to convert.
645
 * @return
646
 *  - NULL if we can't.
647
 *  - Known attribute if we can.
648
 */
649
fr_dict_attr_t const *fr_dict_attr_unknown_resolve(fr_dict_t const *dict, fr_dict_attr_t const *da)
650
0
{
651
0
  INTERNAL_IF_NULL(dict, NULL);
652
653
0
  if (!da->flags.is_unknown) return da; /* It's known */
654
655
0
  if (da->parent) {
656
0
    fr_dict_attr_t const *parent;
657
658
0
    parent = fr_dict_attr_unknown_resolve(dict, da->parent);
659
0
    if (!parent) return NULL;
660
661
0
    return fr_dict_attr_child_by_num(parent, da->attr);
662
0
  }
663
664
0
  if (dict->root == da) return dict->root;
665
0
  return NULL;
666
0
}