]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Break out more parsing functions
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 28 Nov 2024 02:24:08 +0000 (20:24 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 28 Nov 2024 03:10:42 +0000 (21:10 -0600)
src/lib/util/dict_tokenize.c

index c105f83f960806797fa037afa6e723235ad31e9a..ba1582748e4a76b694bee978dc43411b8dabb859 100644 (file)
@@ -682,6 +682,16 @@ static fr_dict_attr_t const *dict_gctx_unwind(dict_tokenize_ctx_t *ctx)
        return ctx->stack[ctx->stack_depth].da;
 }
 
+static int dict_finalise(dict_tokenize_ctx_t *ctx)
+{
+       if (dict_fixup_apply(&ctx->fixup) < 0) return -1;
+
+       ctx->value_attr = NULL;
+       ctx->relative_attr = NULL;
+
+       return 0;
+}
+
 static inline CC_HINT(always_inline)
 void dict_attr_location_set(dict_tokenize_ctx_t *dctx, fr_dict_attr_t *da)
 {
@@ -1246,6 +1256,245 @@ static int dict_read_process_attribute(dict_tokenize_ctx_t *ctx, char **argv, in
        return 0;
 }
 
+static int dict_read_process_begin_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                           UNUSED fr_dict_attr_flags_t *base_flags)
+{
+       fr_dict_t                       *found;
+       dict_tokenize_frame_t const     *frame;
+
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
+       if (argc != 1) {
+               fr_strerror_const_push("Invalid BEGIN-PROTOCOL entry");
+       error:
+               return -1;
+       }
+
+       /*
+        *      If we're not parsing in the context of the internal
+        *      dictionary, then we don't allow BEGIN-PROTOCOL
+        *      statements.
+        */
+       if (dctx->dict != dict_gctx->internal) {
+               fr_strerror_const_push("Nested BEGIN-PROTOCOL statements are not allowed");
+               goto error;
+       }
+
+       found = dict_by_protocol_name(argv[0]);
+       if (!found) {
+               fr_strerror_printf("Unknown protocol '%s'", argv[0]);
+               goto error;
+       }
+
+       frame = dict_gctx_find_frame(dctx, NEST_PROTOCOL);
+       if (frame) {
+               fr_strerror_printf_push("Nested BEGIN-PROTOCOL is forbidden.  Previous definition is at %s[%d]",
+                                       frame->filename, frame->line);
+               goto error;
+       }
+
+       /*
+        *      Add a temporary fixup pool
+        *
+        *      @todo - make a nested ctx?
+        */
+       dict_fixup_init(NULL, &dctx->fixup);
+
+       /*
+        *      We're in the middle of loading this dictionary.  Tell
+        *      fr_dict_protocol_afrom_file() to suppress recursive references.
+        */
+       found->loading = true;
+
+       dctx->dict = found;
+
+       if (dict_gctx_push(dctx, dctx->dict->root) < 0) goto error;
+       dctx->stack[dctx->stack_depth].nest = NEST_PROTOCOL;
+
+       return 0;
+}
+
+static int dict_read_process_begin_tlv(dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                      UNUSED fr_dict_attr_flags_t *base_flags)
+{
+       fr_dict_attr_t const *da;
+       fr_dict_attr_t const *common;
+
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
+       if (argc != 1) {
+               fr_strerror_const_push("Invalid BEGIN-TLV entry");
+       error:
+               return -1;
+       }
+
+       da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, argv[0]);
+       if (!da) {
+               fr_strerror_const_push("Failed resolving attribute in BEGIN-TLV entry");
+               goto error;
+       }
+
+       if (da->type != FR_TYPE_TLV) {
+               fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
+                                       argv[0],
+                                       fr_type_to_str(da->type));
+               goto error;
+       }
+
+       common = fr_dict_attr_common_parent(dctx->stack[dctx->stack_depth].da, da, true);
+       if (!common ||
+               (common->type == FR_TYPE_VSA)) {
+               fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
+                                       argv[0], dctx->stack[dctx->stack_depth].da->name);
+               goto error;
+       }
+
+       if (dict_gctx_push(dctx, da) < 0) goto error;
+       dctx->stack[dctx->stack_depth].nest = NEST_TLV;
+
+       return 0;
+}
+
+static int dict_read_process_begin_vendor(dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                         UNUSED fr_dict_attr_flags_t *base_flags)
+{
+       fr_dict_vendor_t const          *vendor;
+       fr_dict_attr_flags_t            flags;
+
+       fr_dict_attr_t const            *vsa_da;
+       fr_dict_attr_t const            *vendor_da;
+       fr_dict_attr_t                  *new;
+       dict_tokenize_frame_t const     *frame;
+       char                            *p;
+
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
+       if (argc < 1) {
+               fr_strerror_const_push("Invalid BEGIN-VENDOR entry");
+       error:
+               return 01;
+       }
+
+       vendor = fr_dict_vendor_by_name(dctx->dict, argv[0]);
+       if (!vendor) {
+               fr_strerror_printf_push("Unknown vendor '%s'", argv[0]);
+               goto error;
+       }
+
+       /*
+        *      Check for extended attr VSAs
+        *
+        *      BEGIN-VENDOR foo parent=Foo-Encapsulation-Attr
+        */
+       if (argc > 1) {
+               fr_dict_attr_t const *da;
+
+               if (strncmp(argv[1], "parent=", 7) != 0) {
+                       fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[1]);
+                       goto error;
+               }
+
+               p = argv[1] + 7;
+               da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, p);
+               if (!da) {
+                       fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[1]);
+                       goto error;
+               }
+
+               if (da->type != FR_TYPE_VSA) {
+                       fr_strerror_printf_push("Invalid parent for BEGIN-VENDOR.  "
+                                               "Attribute '%s' should be 'vsa' but is '%s'", p,
+                                               fr_type_to_str(da->type));
+                       goto error;
+               }
+
+               vsa_da = da;
+
+       } else if (dctx->dict->vsa_parent) {
+               /*
+                *      Check that the protocol-specific VSA parent exists.
+                */
+               vsa_da = dict_attr_child_by_num(dctx->stack[dctx->stack_depth].da, dctx->dict->vsa_parent);
+               if (!vsa_da) {
+                       fr_strerror_printf_push("Failed finding VSA parent for Vendor %s",
+                                               vendor->name);
+                       goto error;
+               }
+
+       } else if (dctx->dict->string_based) {
+               vsa_da = dctx->dict->root;
+
+       } else {
+               fr_strerror_printf_push("BEGIN-VENDOR is forbidden for protocol %s - it has no ATTRIBUTE of type 'vsa'",
+                                       dctx->dict->root->name);
+               goto error;
+       }
+
+       frame = dict_gctx_find_frame(dctx, NEST_VENDOR);
+       if (frame) {
+               fr_strerror_printf_push("Nested BEGIN-VENDOR is forbidden.  Previous definition is at %s[%d]",
+                                       frame->filename, frame->line);
+               goto error;
+       }
+
+       /*
+        *      Create a VENDOR attribute on the fly, either in the context
+        *      of the VSA (26) attribute.
+        */
+       vendor_da = dict_attr_child_by_num(vsa_da, vendor->pen);
+       if (!vendor_da) {
+               memset(&flags, 0, sizeof(flags));
+
+               flags.type_size = dctx->dict->proto->default_type_size;
+               flags.length = dctx->dict->proto->default_type_length;
+
+               /*
+                *      See if this vendor has
+                *      specific sizes for type /
+                *      length.
+                *
+                *      @todo - Make this more protocol agnostic!
+                */
+               if ((vsa_da->type == FR_TYPE_VSA) &&
+                       (vsa_da->parent->flags.is_root)) {
+                       fr_dict_vendor_t const *dv;
+
+                       dv = fr_dict_vendor_by_num(dctx->dict, vendor->pen);
+                       if (dv) {
+                               flags.type_size = dv->type;
+                               flags.length = dv->length;
+                       }
+               }
+
+               new = dict_attr_alloc(dctx->dict->pool,
+                                     vsa_da, argv[0], vendor->pen, FR_TYPE_VENDOR,
+                                     &(dict_attr_args_t){ .flags = &flags });
+               if (unlikely(!new)) goto error;
+
+               if (dict_attr_child_add(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) {
+                       talloc_free(new);
+                       goto error;
+               }
+
+               if (dict_attr_add_to_namespace(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) {
+                       talloc_free(new);
+                       goto error;
+               }
+
+               vendor_da = new;
+       } else {
+               fr_assert(vendor_da->type == FR_TYPE_VENDOR);
+       }
+
+       if (dict_gctx_push(dctx, vendor_da) < 0) goto error;
+       dctx->stack[dctx->stack_depth].nest = NEST_VENDOR;
+
+       return 0;
+}
+
 /*
  *     Process the DEFINE command
  *
@@ -1364,11 +1613,129 @@ static int dict_read_process_define(dict_tokenize_ctx_t *ctx, char **argv, int a
        return 0;
 }
 
+static int dict_read_process_end_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                         UNUSED fr_dict_attr_flags_t *base_flags)
+{
+       fr_dict_t const *found;
+
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
+       if (argc != 1) {
+               fr_strerror_const("Invalid END-PROTOCOL entry");
+       error:
+               return -1;
+       }
+
+       found = dict_by_protocol_name(argv[0]);
+       if (!found) {
+               fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[0]);
+               goto error;
+       }
+
+       if (found != dctx->dict) {
+               fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
+                                  argv[0], found->root->name);
+               goto error;
+       }
+
+       /*
+        *      Pop the stack until we get to a PROTOCOL nesting.
+        */
+       while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_PROTOCOL)) {
+               if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) {
+                       fr_strerror_printf_push("END-PROTOCOL %s with mismatched BEGIN-??? %s", argv[0],
+                               dctx->stack[dctx->stack_depth].da->name);
+                       goto error;
+               }
+
+               dctx->stack_depth--;
+       }
+
+       if (dctx->stack_depth == 0) {
+               fr_strerror_printf_push("END-PROTOCOL %s with no previous BEGIN-PROTOCOL", argv[0]);
+               goto error;
+       }
+
+       if (found->root != dctx->stack[dctx->stack_depth].da) {
+               fr_strerror_printf_push("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", argv[0],
+                                       dctx->stack[dctx->stack_depth].da->name);
+               goto error;
+       }
+
+       /*
+        *      Applies fixups to any attributes added
+        *      to the protocol dictionary.  Note that
+        *      the finalise function prints out the
+        *      original filename / line of the
+        *      error. So we don't need to do that
+        *      here.
+        */
+       if (dict_finalise(dctx) < 0) goto error;
+
+       dctx->stack_depth--;
+       dctx->dict = dctx->stack[dctx->stack_depth].dict;
+
+       return 0;
+}
+
+static int dict_read_process_end_tlv(dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                    UNUSED fr_dict_attr_flags_t *base_flags)
+{
+       fr_dict_attr_t const *da;
+
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
+       if (argc != 1) {
+               fr_strerror_const_push("Invalid END-TLV entry");
+       error:
+               return -1;
+       }
+
+       da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth - 1].da, argv[0]);
+       if (!da) {
+               fr_strerror_const_push("Failed resolving attribute in END-TLV entry");
+               goto error;
+       }
+
+       /*
+               *       Pop the stack until we get to a TLV nesting.
+               */
+       while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_TLV)) {
+               if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) {
+                       fr_strerror_printf_push("END-TLV %s with mismatched BEGIN-??? %s", argv[0],
+                               dctx->stack[dctx->stack_depth].da->name);
+                       goto error;
+               }
+
+               dctx->stack_depth--;
+       }
+
+       if (dctx->stack_depth == 0) {
+               fr_strerror_printf_push("END-TLV %s with no previous BEGIN-TLV", argv[0]);
+               goto error;
+       }
+
+       if (da != dctx->stack[dctx->stack_depth].da) {
+               fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[0],
+                                       dctx->stack[dctx->stack_depth].da->name);
+               goto error;
+       }
+
+       dctx->stack_depth--;
+
+       return 0;
+}
+
 static int dict_read_process_end_vendor(dict_tokenize_ctx_t *dctx, char **argv, int argc,
                                        UNUSED fr_dict_attr_flags_t *base_flags)
 {
        fr_dict_vendor_t const *vendor;
 
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
        if (argc != 1) {
                fr_strerror_const_push("Invalid END-VENDOR entry");
        error:
@@ -1515,7 +1882,8 @@ static int dict_read_process_enum(dict_tokenize_ctx_t *ctx, char **argv, int arg
 /*
  *     Process the FLAGS command
  */
-static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv, int argc, fr_dict_attr_flags_t *base_flags)
+static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv, int argc,
+                                  fr_dict_attr_flags_t *base_flags)
 {
        bool sense = true;
 
@@ -1538,232 +1906,32 @@ static int dict_read_process_flags(UNUSED dict_tokenize_ctx_t *dctx, char **argv
        return -1;
 }
 
-/*
- *     Process the MEMBER command
+/** Process a STRUCT name attr value
+ *
+ * Define struct 'name' when key 'attr' has 'value'.
+ *
+ *  Which MUST be a sub-structure of another struct
  */
-static int dict_read_process_member(dict_tokenize_ctx_t *ctx, char **argv, int argc,
-                                   fr_dict_attr_flags_t const *base_flags)
+static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int argc,
+                                   UNUSED fr_dict_attr_flags_t *base_flags)
 {
-       fr_dict_attr_t          *da;
-
-       if ((argc < 2) || (argc > 3)) {
-               fr_strerror_const("Invalid MEMBER syntax");
-               return -1;
-       }
-
-       if (ctx->stack[ctx->stack_depth].da->type != FR_TYPE_STRUCT) {
-               fr_strerror_const("MEMBER can only be used for ATTRIBUTEs of type 'struct'");
-               return -1;
-       }
+       fr_value_box_t                  value = FR_VALUE_BOX_INITIALISER_NULL(value);
+       int                             i;
+       fr_dict_attr_t const            *parent = NULL;
+       unsigned int                    attr;
+       char                            *key_attr = argv[1];
+       char                            *name = argv[0];
+       fr_dict_attr_t                  *da;
 
-       if (dict_read_process_common(ctx, &da, argv[0], argv[1],
-                                    (argc > 2) ? argv[2] : NULL, base_flags) < 0) {
+       if ((argc < 3) || (argc > 4)) {
+               fr_strerror_const("Invalid STRUCT syntax");
                return -1;
        }
 
-#ifdef STATIC_ANALYZER
-       if (!ctx->dict) goto error;
-#endif
+       fr_assert(ctx->stack_depth > 0);
 
        /*
-        *      If our parent is a fixed-size struct, then we have to be fixed-size, too.
-        */
-       da->flags.is_known_width |= ctx->stack[ctx->stack_depth].da->flags.is_known_width;
-
-       /*
-        *      Double check bit field magic
-        */
-       if (ctx->stack[ctx->stack_depth].member_num > 0) {
-               fr_dict_attr_t const *previous;
-
-               previous = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da,
-                                                 ctx->stack[ctx->stack_depth].member_num);
-               /*
-                *      Check that the previous bit field ended on a
-                *      byte boundary.
-                *
-                *      Note that the previous attribute might be a deferred TLV, in which case it doesn't
-                *      exist.  That's fine.
-                */
-               if (previous && previous->flags.extra && (previous->flags.subtype == FLAG_BIT_FIELD)) {
-                       /*
-                        *      This attribute is a bit field.  Keep
-                        *      track of where in the byte we are
-                        *      located.
-                        */
-                       if (da->flags.extra && (da->flags.subtype == FLAG_BIT_FIELD)) {
-                               da->flags.flag_byte_offset = (da->flags.length + previous->flags.flag_byte_offset) & 0x07;
-
-                       } else {
-                               if (previous->flags.flag_byte_offset != 0) {
-                                       fr_strerror_printf("Previous bitfield %s did not end on a byte boundary",
-                                                          previous->name);
-                               error:
-                                       talloc_free(da);
-                                       return -1;
-                               }
-                       }
-               }
-       }
-
-       /*
-        *      Check if the parent 'struct' is fixed size.  And if
-        *      so, complain if we're adding a variable sized member.
-        */
-       if (ctx->stack[ctx->stack_depth].struct_is_closed) {
-               fr_strerror_printf("Cannot add MEMBER to 'struct' %s after a variable sized member %s",
-                                  ctx->stack[ctx->stack_depth].da->name,
-                                  ctx->stack[ctx->stack_depth].struct_is_closed->name);
-               goto error;
-       }
-
-       /*
-        *      Ensure that no previous child has "key" or "length" set.
-        */
-       if (da->type == FR_TYPE_TLV) {
-               fr_dict_attr_t const *key;
-               int i;
-
-               /*
-                *      @todo - cache the key field in the stack frame, so we don't have to loop over the children.
-                */
-               for (i = 1; i <= ctx->stack[ctx->stack_depth].member_num; i++) {
-                       key = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, i);
-                       if (!key) continue; /* really should be WTF? */
-
-                       if (fr_dict_attr_is_key_field(key)) {
-                               fr_strerror_printf("'struct' %s has a 'key' field %s, and cannot end with a TLV %s",
-                                                  ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]);
-                               goto error;
-                       }
-
-                       if (da_is_length_field(key)) {
-                               fr_strerror_printf("'struct' %s has a 'length' field %s, and cannot end with a TLV %s",
-                                                  ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]);
-                               goto error;
-                       }
-               }
-       }
-
-       if (unlikely(dict_attr_parent_init(&da, ctx->stack[ctx->stack_depth].da) < 0)) goto error;
-       if (unlikely(dict_attr_num_init(da, ++ctx->stack[ctx->stack_depth].member_num) < 0)) goto error;
-       if (unlikely(dict_attr_finalise(&da, argv[0]) < 0)) goto error;
-
-       /*
-        *      Check to see if this is a duplicate attribute
-        *      and whether we should ignore it or error out...
-        */
-       switch (dict_attr_allow_dup(da)) {
-       case 1:
-               break;
-
-       case 0:
-               talloc_free(da);
-               return 0;
-
-       default:
-               goto error;
-       }
-
-       switch (dict_attr_add_or_fixup(&ctx->fixup, &da)) {
-       default:
-               goto error;
-
-       /* New attribute, fixup stack */
-       case 0:
-               /*
-               *       A 'struct' can have a MEMBER of type 'tlv', but ONLY
-               *       as the last entry in the 'struct'.  If we see that,
-               *       set the previous attribute to the TLV we just added.
-               *       This allows the children of the TLV to be parsed as
-               *       partial OIDs, so we don't need to know the full path
-               *       to them.
-               */
-               if (da->type == FR_TYPE_TLV) {
-                       ctx->relative_attr = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da,
-                                                                   ctx->stack[ctx->stack_depth].member_num);
-                       if (ctx->relative_attr && (dict_gctx_push(ctx, ctx->relative_attr) < 0)) return -1;
-                       return 0;
-
-               }
-
-               /*
-                *      Add the size of this member to the parent struct.
-                */
-               if (ctx->stack[ctx->stack_depth].da->flags.length) {
-                       /*
-                        *      Fixed-size struct can't have MEMBERs of unknown sizes.
-                        */
-                       if (!da->flags.is_known_width) {
-                               fr_strerror_printf("'struct' %s has fixed size %u, but member %s is of unknown size",
-                                                  ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length,
-                                                  argv[0]);
-                               return -1;
-                       }
-
-                       ctx->stack[ctx->stack_depth].struct_size += da->flags.length;
-
-               }
-
-               /*
-                *      Check for overflow.
-                */
-               if (ctx->stack[ctx->stack_depth].da->flags.length &&
-                   (ctx->stack[ctx->stack_depth].struct_size > ctx->stack[ctx->stack_depth].da->flags.length)) {
-                       fr_strerror_printf("'struct' %s has fixed size %u, but member %s overflows that length",
-                                          ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length,
-                                          argv[0]);
-                       return -1;
-               }
-
-               if (dict_set_value_attr(ctx, da) < 0) return -1;
-
-               /*
-                *      Check if this MEMBER closes the structure.
-                *
-                *      @todo - close this struct if the child struct is variable sized.  For now, it
-                *      looks like most child structs are at the end of the parent.
-                *
-                *      The solution is to update the unwind() function to check if the da we've
-                *      unwound to is a struct, and then if so... get the last child, and mark it
-                *      closed.
-                */
-               if (!da->flags.is_known_width) ctx->stack[ctx->stack_depth].struct_is_closed = da;
-               break;
-
-       /* Deferred attribute, don't begin the TLV section automatically */
-       case 1:
-               break;
-       }
-
-       return 0;
-}
-
-/** Process a STRUCT name attr value
- *
- * Define struct 'name' when key 'attr' has 'value'.
- *
- *  Which MUST be a sub-structure of another struct
- */
-static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int argc)
-{
-       fr_value_box_t                  value = FR_VALUE_BOX_INITIALISER_NULL(value);
-       int                             i;
-       fr_dict_attr_t const            *parent = NULL;
-       unsigned int                    attr;
-       char                            *key_attr = argv[1];
-       char                            *name = argv[0];
-       fr_dict_attr_t                  *da;
-
-       if ((argc < 3) || (argc > 4)) {
-               fr_strerror_const("Invalid STRUCT syntax");
-               return -1;
-       }
-
-       fr_assert(ctx->stack_depth > 0);
-
-       /*
-        *      Unwind the stack until we find a parent which has a child named "key_attr"
+        *      Unwind the stack until we find a parent which has a child named "key_attr"
         */
        for (i = ctx->stack_depth; i > 0; i--) {
                parent = dict_attr_by_name(NULL, ctx->stack[i].da, key_attr);
@@ -1875,28 +2043,229 @@ static int dict_read_process_struct(dict_tokenize_ctx_t *ctx, char **argv, int a
        default:
                goto error;
 
-       /* FIXME: Should dict_attr_enum_add_name also be called in the fixup code? */
+       /* FIXME: Should dict_attr_enum_add_name also be called in the fixup code? */
+       case 0:
+               da = dict_attr_by_name(NULL, parent, name);
+               if (!da) return -1;
+
+               /*
+                *      A STRUCT definition is an implicit BEGIN-STRUCT.
+                */
+               ctx->relative_attr = NULL;
+               if (dict_gctx_push(ctx, da) < 0) return -1;
+
+               /*
+               *       Add the VALUE to the parent attribute, and ensure that
+               *       the VALUE also contains a pointer to the child struct.
+               */
+               if (dict_attr_enum_add_name(fr_dict_attr_unconst(parent), name, &value, false, true, da) < 0) {
+                       fr_value_box_clear(&value);
+                       return -1;
+               }
+               fr_value_box_clear(&value);
+               break;
+
+       case 1:
+               break;
+       }
+
+       return 0;
+}
+
+/*
+ *     Process the MEMBER command
+ */
+static int dict_read_process_member(dict_tokenize_ctx_t *ctx, char **argv, int argc,
+                                   fr_dict_attr_flags_t const *base_flags)
+{
+       fr_dict_attr_t          *da;
+
+       if ((argc < 2) || (argc > 3)) {
+               fr_strerror_const("Invalid MEMBER syntax");
+               return -1;
+       }
+
+       if (ctx->stack[ctx->stack_depth].da->type != FR_TYPE_STRUCT) {
+               fr_strerror_const("MEMBER can only be used for ATTRIBUTEs of type 'struct'");
+               return -1;
+       }
+
+       if (dict_read_process_common(ctx, &da, argv[0], argv[1],
+                                    (argc > 2) ? argv[2] : NULL, base_flags) < 0) {
+               return -1;
+       }
+
+#ifdef STATIC_ANALYZER
+       if (!ctx->dict) goto error;
+#endif
+
+       /*
+        *      If our parent is a fixed-size struct, then we have to be fixed-size, too.
+        */
+       da->flags.is_known_width |= ctx->stack[ctx->stack_depth].da->flags.is_known_width;
+
+       /*
+        *      Double check bit field magic
+        */
+       if (ctx->stack[ctx->stack_depth].member_num > 0) {
+               fr_dict_attr_t const *previous;
+
+               previous = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da,
+                                                 ctx->stack[ctx->stack_depth].member_num);
+               /*
+                *      Check that the previous bit field ended on a
+                *      byte boundary.
+                *
+                *      Note that the previous attribute might be a deferred TLV, in which case it doesn't
+                *      exist.  That's fine.
+                */
+               if (previous && previous->flags.extra && (previous->flags.subtype == FLAG_BIT_FIELD)) {
+                       /*
+                        *      This attribute is a bit field.  Keep
+                        *      track of where in the byte we are
+                        *      located.
+                        */
+                       if (da->flags.extra && (da->flags.subtype == FLAG_BIT_FIELD)) {
+                               da->flags.flag_byte_offset = (da->flags.length + previous->flags.flag_byte_offset) & 0x07;
+
+                       } else {
+                               if (previous->flags.flag_byte_offset != 0) {
+                                       fr_strerror_printf("Previous bitfield %s did not end on a byte boundary",
+                                                          previous->name);
+                               error:
+                                       talloc_free(da);
+                                       return -1;
+                               }
+                       }
+               }
+       }
+
+       /*
+        *      Check if the parent 'struct' is fixed size.  And if
+        *      so, complain if we're adding a variable sized member.
+        */
+       if (ctx->stack[ctx->stack_depth].struct_is_closed) {
+               fr_strerror_printf("Cannot add MEMBER to 'struct' %s after a variable sized member %s",
+                                  ctx->stack[ctx->stack_depth].da->name,
+                                  ctx->stack[ctx->stack_depth].struct_is_closed->name);
+               goto error;
+       }
+
+       /*
+        *      Ensure that no previous child has "key" or "length" set.
+        */
+       if (da->type == FR_TYPE_TLV) {
+               fr_dict_attr_t const *key;
+               int i;
+
+               /*
+                *      @todo - cache the key field in the stack frame, so we don't have to loop over the children.
+                */
+               for (i = 1; i <= ctx->stack[ctx->stack_depth].member_num; i++) {
+                       key = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da, i);
+                       if (!key) continue; /* really should be WTF? */
+
+                       if (fr_dict_attr_is_key_field(key)) {
+                               fr_strerror_printf("'struct' %s has a 'key' field %s, and cannot end with a TLV %s",
+                                                  ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]);
+                               goto error;
+                       }
+
+                       if (da_is_length_field(key)) {
+                               fr_strerror_printf("'struct' %s has a 'length' field %s, and cannot end with a TLV %s",
+                                                  ctx->stack[ctx->stack_depth].da->name, key->name, argv[0]);
+                               goto error;
+                       }
+               }
+       }
+
+       if (unlikely(dict_attr_parent_init(&da, ctx->stack[ctx->stack_depth].da) < 0)) goto error;
+       if (unlikely(dict_attr_num_init(da, ++ctx->stack[ctx->stack_depth].member_num) < 0)) goto error;
+       if (unlikely(dict_attr_finalise(&da, argv[0]) < 0)) goto error;
+
+       /*
+        *      Check to see if this is a duplicate attribute
+        *      and whether we should ignore it or error out...
+        */
+       switch (dict_attr_allow_dup(da)) {
+       case 1:
+               break;
+
+       case 0:
+               talloc_free(da);
+               return 0;
+
+       default:
+               goto error;
+       }
+
+       switch (dict_attr_add_or_fixup(&ctx->fixup, &da)) {
+       default:
+               goto error;
+
+       /* New attribute, fixup stack */
        case 0:
-               da = dict_attr_by_name(NULL, parent, name);
-               if (!da) return -1;
+               /*
+               *       A 'struct' can have a MEMBER of type 'tlv', but ONLY
+               *       as the last entry in the 'struct'.  If we see that,
+               *       set the previous attribute to the TLV we just added.
+               *       This allows the children of the TLV to be parsed as
+               *       partial OIDs, so we don't need to know the full path
+               *       to them.
+               */
+               if (da->type == FR_TYPE_TLV) {
+                       ctx->relative_attr = dict_attr_child_by_num(ctx->stack[ctx->stack_depth].da,
+                                                                   ctx->stack[ctx->stack_depth].member_num);
+                       if (ctx->relative_attr && (dict_gctx_push(ctx, ctx->relative_attr) < 0)) return -1;
+                       return 0;
+
+               }
 
                /*
-                *      A STRUCT definition is an implicit BEGIN-STRUCT.
+                *      Add the size of this member to the parent struct.
                 */
-               ctx->relative_attr = NULL;
-               if (dict_gctx_push(ctx, da) < 0) return -1;
+               if (ctx->stack[ctx->stack_depth].da->flags.length) {
+                       /*
+                        *      Fixed-size struct can't have MEMBERs of unknown sizes.
+                        */
+                       if (!da->flags.is_known_width) {
+                               fr_strerror_printf("'struct' %s has fixed size %u, but member %s is of unknown size",
+                                                  ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length,
+                                                  argv[0]);
+                               return -1;
+                       }
+
+                       ctx->stack[ctx->stack_depth].struct_size += da->flags.length;
+
+               }
 
                /*
-               *       Add the VALUE to the parent attribute, and ensure that
-               *       the VALUE also contains a pointer to the child struct.
-               */
-               if (dict_attr_enum_add_name(fr_dict_attr_unconst(parent), name, &value, false, true, da) < 0) {
-                       fr_value_box_clear(&value);
+                *      Check for overflow.
+                */
+               if (ctx->stack[ctx->stack_depth].da->flags.length &&
+                   (ctx->stack[ctx->stack_depth].struct_size > ctx->stack[ctx->stack_depth].da->flags.length)) {
+                       fr_strerror_printf("'struct' %s has fixed size %u, but member %s overflows that length",
+                                          ctx->stack[ctx->stack_depth].da->name, ctx->stack[ctx->stack_depth].da->flags.length,
+                                          argv[0]);
                        return -1;
                }
-               fr_value_box_clear(&value);
+
+               if (dict_set_value_attr(ctx, da) < 0) return -1;
+
+               /*
+                *      Check if this MEMBER closes the structure.
+                *
+                *      @todo - close this struct if the child struct is variable sized.  For now, it
+                *      looks like most child structs are at the end of the parent.
+                *
+                *      The solution is to update the unwind() function to check if the da we've
+                *      unwound to is a struct, and then if so... get the last child, and mark it
+                *      closed.
+                */
+               if (!da->flags.is_known_width) ctx->stack[ctx->stack_depth].struct_is_closed = da;
                break;
 
+       /* Deferred attribute, don't begin the TLV section automatically */
        case 1:
                break;
        }
@@ -2010,6 +2379,9 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int
        fr_dict_vendor_t                *mutable;
        fr_dict_t                       *dict = dctx->dict;
 
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
        if ((argc < 2) || (argc > 3)) {
                fr_strerror_const("Invalid VENDOR syntax");
                return -1;
@@ -2023,9 +2395,6 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int
                return -1;
        }
 
-       dctx->value_attr = NULL;
-       dctx->relative_attr = NULL;
-
        /*
         *      Look for a format statement.  Allow it to over-ride the hard-coded formats below.
         */
@@ -2057,7 +2426,7 @@ static int dict_read_process_vendor(dict_tokenize_ctx_t *dctx, char **argv, int
  *
  * Allows vendor and TLV context to persist across $INCLUDEs
  */
-static int dict_read_process_protocol(char **argv, int argc)
+static int dict_read_process_protocol(dict_tokenize_ctx_t *dctx, char **argv, int argc, UNUSED fr_dict_attr_flags_t *base_flag)
 {
        unsigned int    value;
        unsigned int    type_size = 0;
@@ -2066,6 +2435,9 @@ static int dict_read_process_protocol(char **argv, int argc)
        bool            require_dl = false;
        bool            string_based = false;
 
+       dctx->value_attr = NULL;
+       dctx->relative_attr = NULL;
+
        if ((argc < 2) || (argc > 3)) {
                fr_strerror_const("Missing arguments after PROTOCOL.  Expected PROTOCOL <num> <name>");
                return -1;
@@ -2197,16 +2569,6 @@ post_option:
        return 0;
 }
 
-static int dict_finalise(dict_tokenize_ctx_t *ctx)
-{
-       if (dict_fixup_apply(&ctx->fixup) < 0) return -1;
-
-       ctx->value_attr = NULL;
-       ctx->relative_attr = NULL;
-
-       return 0;
-}
-
 /** Maintain a linked list of filenames which we've seen loading this dictionary
  *
  * This is used for debug messages, so we have a copy of the original file path
@@ -2294,10 +2656,17 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx,
        static fr_dict_keyword_t const keywords[] = {
                { L("ALIAS"),                   { dict_read_process_alias } },
                { L("ATTRIBUTE"),               { dict_read_process_attribute } },
+               { L("BEGIN-PROTOCOL"),          { dict_read_process_begin_protocol } },
+               { L("BEGIN-TLV"),               { dict_read_process_begin_tlv } },
+               { L("BEGIN-VENDOR"),            { dict_read_process_begin_vendor } },
                { L("DEFINE"),                  { dict_read_process_define } },
+               { L("END-PROTOCOL"),            { dict_read_process_end_protocol } },
+               { L("END-TLV"),                 { dict_read_process_end_tlv } },
                { L("END-VENDOR"),              { dict_read_process_end_vendor } },
                { L("ENUM"),                    { dict_read_process_enum } },
                { L("FLAGS"),                   { dict_read_process_flags } },
+               { L("PROTOCOL"),                { dict_read_process_protocol }},
+               { L("STRUCT"),                  { dict_read_process_struct } },
                { L("VALUE"),                   { dict_read_process_value } },
                { L("VENDOR"),                  { dict_read_process_vendor } },
        };
@@ -2430,8 +2799,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx,
 
        while (fgets(buf, sizeof(buf), fp) != NULL) {
                fr_dict_keyword_parser_t const  *parser;
-               dict_tokenize_frame_t const     *frame;
-
 
                dctx->stack[dctx->stack_depth].line = ++line;
 
@@ -2522,14 +2889,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx,
                        was_member = false;
                }
 
-               /*
-                *      Process STRUCT lines.
-                */
-               if (strcasecmp(argv[0], "STRUCT") == 0) {
-                       if (dict_read_process_struct(dctx, argv + 1, argc - 1) == -1) goto error;
-                       continue;
-               }
-
                /*
                 *      See if we need to import another dictionary.
                 */
@@ -2548,348 +2907,6 @@ static int _dict_from_file(dict_tokenize_ctx_t *dctx,
                        continue;
                } /* $INCLUDE */
 
-               /*
-                *      Reset the previous attribute when we see
-                *      VENDOR or PROTOCOL or BEGIN/END-VENDOR, etc.
-                */
-               dctx->value_attr = NULL;
-               dctx->relative_attr = NULL;
-
-               /*
-                *      Process PROTOCOL line.  Defines a new protocol.
-                */
-               if (strcasecmp(argv[0], "PROTOCOL") == 0) {
-                       if (argc < 2) {
-                               fr_strerror_const_push("Invalid PROTOCOL entry");
-                               goto error;
-                       }
-                       if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error;
-                       continue;
-               }
-
-               /*
-                *      Switches the current protocol context
-                */
-               if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) {
-                       fr_dict_t *found;
-
-                       if (argc != 2) {
-                               fr_strerror_const_push("Invalid BEGIN-PROTOCOL entry");
-                               goto error;
-                       }
-
-                       /*
-                        *      If we're not parsing in the context of the internal
-                        *      dictionary, then we don't allow BEGIN-PROTOCOL
-                        *      statements.
-                        */
-                       if (dctx->dict != dict_gctx->internal) {
-                               fr_strerror_const_push("Nested BEGIN-PROTOCOL statements are not allowed");
-                               goto error;
-                       }
-
-                       found = dict_by_protocol_name(argv[1]);
-                       if (!found) {
-                               fr_strerror_printf("Unknown protocol '%s'", argv[1]);
-                               goto error;
-                       }
-
-                       frame = dict_gctx_find_frame(dctx, NEST_PROTOCOL);
-                       if (frame) {
-                               fr_strerror_printf_push("Nested BEGIN-PROTOCOL is forbidden.  Previous definition is at %s[%d]",
-                                                       frame->filename, frame->line);
-                               goto error;
-                       }
-
-                       /*
-                        *      Add a temporary fixup pool
-                        *
-                        *      @todo - make a nested ctx?
-                        */
-                       dict_fixup_init(NULL, &dctx->fixup);
-
-                       /*
-                        *      We're in the middle of loading this dictionary.  Tell
-                        *      fr_dict_protocol_afrom_file() to suppress recursive references.
-                        */
-                       found->loading = true;
-
-                       dctx->dict = found;
-
-                       if (dict_gctx_push(dctx, dctx->dict->root) < 0) goto error;
-                       dctx->stack[dctx->stack_depth].nest = NEST_PROTOCOL;
-                       continue;
-               }
-
-               /*
-                *      Switches back to the previous protocol context
-                */
-               if (strcasecmp(argv[0], "END-PROTOCOL") == 0) {
-                       fr_dict_t const *found;
-
-                       if (argc != 2) {
-                               fr_strerror_const("Invalid END-PROTOCOL entry");
-                               goto error;
-                       }
-
-                       found = dict_by_protocol_name(argv[1]);
-                       if (!found) {
-                               fr_strerror_printf("END-PROTOCOL %s does not refer to a valid protocol", argv[1]);
-                               goto error;
-                       }
-
-                       if (found != dctx->dict) {
-                               fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
-                                                  argv[1], found->root->name);
-                               goto error;
-                       }
-
-                       /*
-                        *      Pop the stack until we get to a PROTOCOL nesting.
-                        */
-                       while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_PROTOCOL)) {
-                               if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) {
-                                       fr_strerror_printf_push("END-PROTOCOL %s with mismatched BEGIN-??? %s", argv[1],
-                                               dctx->stack[dctx->stack_depth].da->name);
-                                       goto error;
-                               }
-
-                               dctx->stack_depth--;
-                       }
-
-                       if (dctx->stack_depth == 0) {
-                               fr_strerror_printf_push("END-PROTOCOL %s with no previous BEGIN-PROTOCOL", argv[1]);
-                               goto error;
-                       }
-
-                       if (found->root != dctx->stack[dctx->stack_depth].da) {
-                               fr_strerror_printf_push("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s", argv[1],
-                                                       dctx->stack[dctx->stack_depth].da->name);
-                               goto error;
-                       }
-
-                       /*
-                        *      Applies fixups to any attributes added
-                        *      to the protocol dictionary.  Note that
-                        *      the finalise function prints out the
-                        *      original filename / line of the
-                        *      error. So we don't need to do that
-                        *      here.
-                        */
-                       if (dict_finalise(dctx) < 0) goto error;
-
-                       dctx->stack_depth--;
-                       dctx->dict = dctx->stack[dctx->stack_depth].dict;
-                       continue;
-               }
-
-               /*
-                *      Switches TLV parent context
-                */
-               if (strcasecmp(argv[0], "BEGIN-TLV") == 0) {
-                       fr_dict_attr_t const *common;
-
-                       if (argc != 2) {
-                               fr_strerror_const_push("Invalid BEGIN-TLV entry");
-                               goto error;
-                       }
-
-                       da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, argv[1]);
-                       if (!da) {
-                               fr_strerror_const_push("Failed resolving attribute in BEGIN-TLV entry");
-                               goto error;
-                       }
-
-                       if (da->type != FR_TYPE_TLV) {
-                               fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
-                                                       argv[1],
-                                                       fr_type_to_str(da->type));
-                               goto error;
-                       }
-
-                       common = fr_dict_attr_common_parent(dctx->stack[dctx->stack_depth].da, da, true);
-                       if (!common ||
-                           (common->type == FR_TYPE_VSA)) {
-                               fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
-                                                       argv[1], dctx->stack[dctx->stack_depth].da->name);
-                               goto error;
-                       }
-
-                       if (dict_gctx_push(dctx, da) < 0) goto error;
-                       dctx->stack[dctx->stack_depth].nest = NEST_TLV;
-                       continue;
-               } /* BEGIN-TLV */
-
-               /*
-                *      Switches back to previous TLV parent
-                */
-               if (strcasecmp(argv[0], "END-TLV") == 0) {
-                       if (argc != 2) {
-                               fr_strerror_const_push("Invalid END-TLV entry");
-                               goto error;
-                       }
-
-                       da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth - 1].da, argv[1]);
-                       if (!da) {
-                               fr_strerror_const_push("Failed resolving attribute in END-TLV entry");
-                               goto error;
-                       }
-
-                       /*
-                        *      Pop the stack until we get to a TLV nesting.
-                        */
-                       while ((dctx->stack_depth > 0) && (dctx->stack[dctx->stack_depth].nest != NEST_TLV)) {
-                               if (dctx->stack[dctx->stack_depth].nest != NEST_NONE) {
-                                       fr_strerror_printf_push("END-TLV %s with mismatched BEGIN-??? %s", argv[1],
-                                               dctx->stack[dctx->stack_depth].da->name);
-                                       goto error;
-                               }
-
-                               dctx->stack_depth--;
-                       }
-
-                       if (dctx->stack_depth == 0) {
-                               fr_strerror_printf_push("END-TLV %s with no previous BEGIN-TLV", argv[1]);
-                               goto error;
-                       }
-
-                       if (da != dctx->stack[dctx->stack_depth].da) {
-                               fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1],
-                                                       dctx->stack[dctx->stack_depth].da->name);
-                               goto error;
-                       }
-
-                       dctx->stack_depth--;
-                       continue;
-               } /* END-VENDOR */
-
-               if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) {
-                       fr_dict_vendor_t const  *vendor;
-                       fr_dict_attr_flags_t    flags;
-
-                       fr_dict_attr_t const    *vsa_da;
-                       fr_dict_attr_t const    *vendor_da;
-                       fr_dict_attr_t          *new;
-
-                       if (argc < 2) {
-                               fr_strerror_const_push("Invalid BEGIN-VENDOR entry");
-                               goto error;
-                       }
-
-                       vendor = fr_dict_vendor_by_name(dctx->dict, argv[1]);
-                       if (!vendor) {
-                               fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
-                               goto error;
-                       }
-
-                       /*
-                        *      Check for extended attr VSAs
-                        *
-                        *      BEGIN-VENDOR foo parent=Foo-Encapsulation-Attr
-                        */
-                       if (argc > 2) {
-                               if (strncmp(argv[2], "parent=", 7) != 0) {
-                                       fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[2]);
-                                       goto error;
-                               }
-
-                               p = argv[2] + 7;
-                               da = fr_dict_attr_by_oid(NULL, dctx->stack[dctx->stack_depth].da, p);
-                               if (!da) {
-                                       fr_strerror_printf_push("BEGIN-VENDOR invalid argument (%s)", argv[2]);
-                                       goto error;
-                               }
-
-                               if (da->type != FR_TYPE_VSA) {
-                                       fr_strerror_printf_push("Invalid parent for BEGIN-VENDOR.  "
-                                                               "Attribute '%s' should be 'vsa' but is '%s'", p,
-                                                               fr_type_to_str(da->type));
-                                       goto error;
-                               }
-
-                               vsa_da = da;
-
-                       } else if (dctx->dict->vsa_parent) {
-                               /*
-                                *      Check that the protocol-specific VSA parent exists.
-                                */
-                               vsa_da = dict_attr_child_by_num(dctx->stack[dctx->stack_depth].da, dctx->dict->vsa_parent);
-                               if (!vsa_da) {
-                                       fr_strerror_printf_push("Failed finding VSA parent for Vendor %s",
-                                                               vendor->name);
-                                       goto error;
-                               }
-
-                       } else if (dctx->dict->string_based) {
-                               vsa_da = dctx->dict->root;
-
-                       } else {
-                               fr_strerror_printf_push("BEGIN-VENDOR is forbidden for protocol %s - it has no ATTRIBUTE of type 'vsa'",
-                                                       dctx->dict->root->name);
-                               goto error;
-                       }
-
-                       frame = dict_gctx_find_frame(dctx, NEST_VENDOR);
-                       if (frame) {
-                               fr_strerror_printf_push("Nested BEGIN-VENDOR is forbidden.  Previous definition is at %s[%d]",
-                                                       frame->filename, frame->line);
-                               goto error;
-                       }
-
-                       /*
-                        *      Create a VENDOR attribute on the fly, either in the context
-                        *      of the VSA (26) attribute.
-                        */
-                       vendor_da = dict_attr_child_by_num(vsa_da, vendor->pen);
-                       if (!vendor_da) {
-                               memset(&flags, 0, sizeof(flags));
-
-                               flags.type_size = dctx->dict->proto->default_type_size;
-                               flags.length = dctx->dict->proto->default_type_length;
-
-                               /*
-                                *      See if this vendor has
-                                *      specific sizes for type /
-                                *      length.
-                                *
-                                *      @todo - Make this more protocol agnostic!
-                                */
-                               if ((vsa_da->type == FR_TYPE_VSA) &&
-                                   (vsa_da->parent->flags.is_root)) {
-                                       fr_dict_vendor_t const *dv;
-
-                                       dv = fr_dict_vendor_by_num(dctx->dict, vendor->pen);
-                                       if (dv) {
-                                               flags.type_size = dv->type;
-                                               flags.length = dv->length;
-                                       }
-                               }
-
-                               new = dict_attr_alloc(dctx->dict->pool,
-                                                     vsa_da, argv[1], vendor->pen, FR_TYPE_VENDOR,
-                                                     &(dict_attr_args_t){ .flags = &flags });
-                               if (unlikely(!new)) goto error;
-
-                               if (dict_attr_child_add(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) {
-                                       talloc_free(new);
-                                       goto error;
-                               }
-
-                               if (dict_attr_add_to_namespace(UNCONST(fr_dict_attr_t *, vsa_da), new) < 0) {
-                                       talloc_free(new);
-                                       goto error;
-                               }
-
-                               vendor_da = new;
-                       } else {
-                               fr_assert(vendor_da->type == FR_TYPE_VENDOR);
-                       }
-
-                       if (dict_gctx_push(dctx, vendor_da) < 0) goto error;
-                       dctx->stack[dctx->stack_depth].nest = NEST_VENDOR;
-                       continue;
-               } /* BEGIN-VENDOR */
-
                /*
                 *      Any other string: We don't recognize it.
                 */