]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Reorganise dict.c
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 19 Mar 2018 11:23:41 +0000 (11:23 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 19 Mar 2018 11:23:41 +0000 (11:23 +0000)
src/include/dict.h
src/lib/util/dict.c

index 1336583096eed0bd3e8df7910be9fdf53b6466d0..61e7b6031c2313bc7f4b4dab565223d1324078c8 100644 (file)
@@ -174,25 +174,10 @@ typedef struct {
 extern bool const      fr_dict_attr_allowed_chars[UINT8_MAX];
 extern bool const      fr_dict_non_data_types[FR_TYPE_MAX + 1];
 
-/*
- *     Dictionary debug
- */
-void                   fr_dict_dump(fr_dict_t *dict);
-
-/*
- *     Dictionary population
+/** @name Programatically create dictionary attributes and values
+ *
+ * @{
  */
-int                    fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out,
-                                         char const *dir, char const *fn, char const *name);
-
-int                    fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
-                                                   char const *dir, char const *internal_name);
-
-int                    fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
-                                                   char const *dir, char const *proto_name);
-
-int                    fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename);
-
 int                    fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent, char const *name, int attr,
                                         fr_type_t type, fr_dict_attr_flags_t const *flags);
 
@@ -200,14 +185,11 @@ int                       fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
                                               fr_value_box_t const *value, bool coerce, bool replace);
 
 int                    fr_dict_str_to_argv(char *str, char **argv, int max_argc);
+/** @} */
 
-int                    fr_dict_parse_str(fr_dict_t *dict, char *buf,
-                                         fr_dict_attr_t const *parent, unsigned int vendor);
-
-fr_dict_attr_t const   *fr_dict_root(fr_dict_t const *dict);
-
-/*
- *     Unknown ephemeral attributes
+/** @name Unknown ephemeral attributes
+ *
+ * @{
  */
 fr_dict_attr_t         *fr_dict_unknown_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *da);
 
@@ -228,9 +210,11 @@ ssize_t                    fr_dict_unknown_afrom_oid_substr(TALLOC_CTX *ctx, fr_dict_attr_t **out
                                                         fr_dict_attr_t const *parent, char const *name);
 
 fr_dict_attr_t const   *fr_dict_attr_known(fr_dict_t *dict, fr_dict_attr_t const *da);
+/** @} */
 
-/*
- *     Lineage
+/** @name Attribute lineage
+ *
+ * @{
  */
 void                   fr_dict_print(fr_dict_attr_t const *da, int depth);
 
@@ -243,10 +227,14 @@ size_t                    fr_dict_print_attr_oid(char *buffer, size_t outlen,
 
 ssize_t                        fr_dict_attr_by_oid(fr_dict_t *dict, fr_dict_attr_t const **parent,
                                            unsigned int *attr, char const *oid);
+/** @} */
 
-/*
- *     Lookup
+/** @name Attribute, vendor and dictionary lookup
+ *
+ * @{
  */
+fr_dict_attr_t const   *fr_dict_root(fr_dict_t const *dict);
+
 fr_dict_t              *fr_dict_by_protocol_name(char const *name);
 
 fr_dict_t              *fr_dict_by_protocol_num(unsigned int num);
@@ -320,13 +308,38 @@ char const                *fr_dict_enum_alias_by_value(fr_dict_t *dict, fr_dict_attr_t const *
                                                     fr_value_box_t const *value);
 
 fr_dict_enum_t         *fr_dict_enum_by_alias(fr_dict_t *dict, fr_dict_attr_t const *da, char const *alias);
+/** @} */
 
-/*
- *     Validation
+/** @name Dictionary and protocol loading
+ *
+ * @{
  */
+int                    fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out,
+                                         char const *dir, char const *fn, char const *name);
+
+int                    fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
+                                                   char const *dir, char const *internal_name);
+
+int                    fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
+                                                   char const *dir, char const *proto_name);
+
+int                    fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename);
+/** @} */
+
+/** @name Dictionary testing and validation
+ *
+ * @{
+ */
+void                   fr_dict_dump(fr_dict_t *dict);
+
+int                    fr_dict_parse_str(fr_dict_t *dict, char *buf,
+                                         fr_dict_attr_t const *parent, unsigned int vendor);
+
 ssize_t                        fr_dict_valid_name(char const *name, ssize_t len);
 
 void                   fr_dict_verify(char const *file, int line, fr_dict_attr_t const *da);
+/** @} */
+
 #ifdef __cplusplus
 }
 #endif
index 8d46db844ae96847809b5a07b2b4147ada911822..672a58cafb9bea752655e8453730ffde527d10f6 100644 (file)
@@ -257,11 +257,6 @@ bool const fr_dict_non_data_types[FR_TYPE_MAX + 1] = {
 #  define INTERNAL_IF_NULL(_dict) if (!_dict) _dict = fr_dict_internal
 #endif
 
-static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child);
-static int dict_from_file(fr_dict_t *dict,
-                         char const *dir_name, char const *filename,
-                         char const *src_file, int src_line);
-
 /** Empty callback for hash table initialization
  *
  */
@@ -543,56 +538,6 @@ static int dict_stat_check(fr_dict_t *dict, char const *dir, char const *file)
        return 0;
 }
 
-static void _fr_dict_dump(fr_dict_attr_t const *da, unsigned int lvl)
-{
-       unsigned int            i;
-       size_t                  len;
-       fr_dict_attr_t const    *p;
-
-       printf("%p - %s (%u) %s\n", da, da->name, da->attr, fr_int2str(dict_attr_types, da->type, "<INVALID>"));
-
-       len = talloc_array_length(da->children);
-       for (i = 0; i < len; i++) {
-               for (p = da->children[i]; p; p = p->next) {
-                       _fr_dict_dump(p, lvl + 1);
-               }
-       }
-
-}
-
-void fr_dict_dump(fr_dict_t *dict)
-{
-       _fr_dict_dump(dict->root, 0);
-}
-
-/** Initialise the global protocol hashes
- *
- * @note Must be called before any other dictionary functions.
- *
- *
- * @param[in] ctx to allocate the hashes in.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int dict_global_init(TALLOC_CTX *ctx)
-{
-       if (protocol_by_name && protocol_by_num) return 0;
-
-       protocol_by_name = fr_hash_table_create(ctx, dict_protocol_name_hash, dict_protocol_name_cmp, NULL);
-       if (!protocol_by_name) {
-               fr_strerror_printf("Failed initializing protocol_by_name hash");
-               return -1;
-       }
-       protocol_by_num = fr_hash_table_create(ctx, dict_protocol_num_hash, dict_protocol_num_cmp, NULL);
-       if (!protocol_by_num) {
-               fr_strerror_printf("Failed initializing protocol_by_num hash");
-               return -1;
-       }
-
-       return 0;
-}
-
 /** Validate a new attribute definition
  *
  * @todo we need to check length of none vendor attributes.
@@ -1350,3840 +1295,3889 @@ static fr_dict_attr_t *dict_attr_ref_alloc(fr_dict_t *dict, fr_dict_attr_t const
        return (fr_dict_attr_t *)ref_n;
 }
 
-/** Set a new root dictionary attribute
+/** Add a protocol to the global protocol table
  *
- * @note Must only be called once per dictionary.
+ * Inserts a protocol into the global protocol table.  Uses the root attributes
+ * of the dictionary for comparisons.
  *
- * @param[in] dict             to modify.
- * @param[in] name             of dictionary root.
- * @param[in] protocol_number  The artificial (or IANA allocated) number for the protocol.
- *                             This is only used for
+ * @param[in] dict of protocol we're inserting.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
+static int dict_protocol_add(fr_dict_t *dict)
 {
-       fr_dict_attr_flags_t flags = {
-               .is_root = 1,
-               .type_size = 1,
-               .length = 1
-       };
+       if (!dict->root) return -1;     /* Should always have root */
 
-       if (!fr_cond_assert(!dict->root)) {
-               fr_strerror_printf("Dictionary root already set");
-               return -1;
-       }
+       if (!fr_hash_table_insert(protocol_by_name, dict)) {
+               fr_dict_t *old_proto;
 
-       dict->root = dict_attr_alloc_name(dict, name);
-       if (!dict->root) return -1;
+               old_proto = fr_hash_table_finddata(protocol_by_name, dict);
+               if (!old_proto) {
+                       fr_strerror_printf("%s: Failed inserting protocol name %s", __FUNCTION__, dict->root->name);
+                       return -1;
+               }
 
-       dict_attr_init(dict->root, NULL, proto_number, FR_TYPE_TLV, &flags);
-       VERIFY_DA(dict->root);
+               if ((strcmp(old_proto->root->name, dict->root->name) == 0) &&
+                   (old_proto->root->name == dict->root->name)) {
+                       fr_strerror_printf("%s: Duplicate protocol name %s", __FUNCTION__, dict->root->name);
+                       return -1;
+               }
+
+               return 0;
+       }
+
+       if (!fr_hash_table_insert(protocol_by_num, dict)) {
+               fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
+               return -1;
+       }
 
        return 0;
 }
 
-/** Allocate a new dictionary
+/** Add a vendor to the dictionary
  *
- * @param[in] ctx to allocate dictionary in.
+ * Inserts a vendor entry into the vendor hash table.  This must be done before adding
+ * attributes under a VSA.
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] name             of the vendor.
+ * @param[in] num              Vendor's Private Enterprise Number.
  * @return
- *     - NULL on memory allocation error.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
+static int dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
 {
-       fr_dict_t *dict;
+       INTERNAL_IF_NULL(dict);
+       size_t                  len;
+       fr_dict_vendor_t        *vendor;
 
-       dict = talloc_zero(ctx, fr_dict_t);
-       if (!dict) {
-       error:
-               fr_strerror_printf("Failed allocating memory for dictionary");
-               talloc_free(dict);
-               return NULL;
+       len = strlen(name);
+       if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
+               fr_strerror_printf("%s: Vendor name too long", __FUNCTION__);
+               return -1;
        }
 
-       /*
-        *      Pre-Allocate 5MB of pool memory for rapid startup
-        */
-       dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
-       if (!dict->pool) goto error;
+#ifdef HAVE_TALLOC_POOLED_OBJECT
+       vendor = talloc_pooled_object(dict, fr_dict_vendor_t, 1, strlen(name) + 1);
+       memset(vendor, 0, sizeof(*vendor));
+#else
+       vendor = talloc_zero(dict, fr_dict_vendor_t);
+#endif
 
-       /*
-        *      Create the table of vendor by name.   There MAY NOT
-        *      be multiple vendors of the same name.
-        */
-       dict->vendors_by_name = fr_hash_table_create(dict, dict_vendor_name_hash, dict_vendor_name_cmp, hash_pool_free);
-       if (!dict->vendors_by_name) goto error;
+       vendor->name = talloc_typed_strdup(vendor, name);
+       if (!vendor->name) {
+               talloc_free(vendor);
+               fr_strerror_printf("Out of memory");
+               return -1;
+       }
+       vendor->vendorpec = num;
+       vendor->type = vendor->length = 1; /* defaults */
 
-       /*
-        *      Create the table of vendors by value.  There MAY
-        *      be vendors of the same value.  If there are, we
-        *      pick the latest one.
-        */
-       dict->vendors_by_num = fr_hash_table_create(dict, dict_vendor_vendorpec_hash, dict_vendor_vendorpec_cmp, NULL);
-       if (!dict->vendors_by_num) goto error;
+       if (!fr_hash_table_insert(dict->vendors_by_name, vendor)) {
+               fr_dict_vendor_t *old_vendor;
 
-       /*
-        *      Create the table of attributes by name.   There MAY NOT
-        *      be multiple attributes of the same name.
-        */
-       dict->attributes_by_name = fr_hash_table_create(dict, dict_attr_name_hash, dict_attr_name_cmp, NULL);
-       if (!dict->attributes_by_name) goto error;
+               old_vendor = fr_hash_table_finddata(dict->vendors_by_name, vendor);
+               if (!old_vendor) {
+                       fr_strerror_printf("%s: Failed inserting vendor name %s", __FUNCTION__, name);
+                       return -1;
+               }
+               if ((strcmp(old_vendor->name, vendor->name) == 0) && (old_vendor->vendorpec != vendor->vendorpec)) {
+                       fr_strerror_printf("%s: Duplicate vendor name %s", __FUNCTION__, name);
+                       return -1;
+               }
 
-       /*
-        *      Horrible hacks for combo-IP.
-        */
-       dict->attributes_combo = fr_hash_table_create(dict, dict_attr_combo_hash, dict_attr_combo_cmp, hash_pool_free);
-       if (!dict->attributes_combo) goto error;
+               /*
+                *      Already inserted.  Discard the duplicate entry.
+                */
+               talloc_free(vendor);
 
-       dict->values_by_alias = fr_hash_table_create(dict, dict_enum_alias_hash, dict_enum_alias_cmp, hash_pool_free);
-       if (!dict->values_by_alias) goto error;
+               return 0;
+       }
 
-       dict->values_by_da = fr_hash_table_create(dict, dict_enum_value_hash, dict_enum_value_cmp, hash_pool_free);
-       if (!dict->values_by_da) goto error;
+       /*
+        *      Insert the SAME pointer (not free'd when this table is
+        *      deleted), into another table.
+        *
+        *      We want this behaviour because we want OLD names for
+        *      the attributes to be read from the configuration
+        *      files, but when we're printing them, (and looking up
+        *      by value) we want to use the NEW name.
+        */
+       if (!fr_hash_table_replace(dict->vendors_by_num, vendor)) {
+               fr_strerror_printf("%s: Failed inserting vendor %s", __FUNCTION__, name);
+               return -1;
+       }
 
-       return dict;
+       return 0;
 }
 
-/** (re)initialize a protocol dictionary
- *
- * Initialize the directory, then fix the attr member of all attributes.
- *
- * First dictionary initialised will be set as the default internal dictionary.
+/** Add a child to a parent.
  *
- * @param[in] ctx              to allocate the dictionary from.
- * @param[out] out             Where to write a pointer to the new dictionary.
- *                             Will free existing dictionary if files have
- *                             changed and *out is not NULL.
- * @param[in] dir              to read dictionary files from.
- * @param[in] fn               file name to read.
- * @param[in] name             to use for the root attributes.
+ * @param[in] parent   we're adding a child to.
+ * @param[in] child    to add to parent.
  * @return
  *     - 0 on success.
- *     - -1 on failure.
+ *     - -1 on failure (memory allocation error).
  */
-int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
+static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child)
 {
-       static bool     defined_cast_types;
-       fr_dict_t       *dict;
-
-       dict = dict_alloc(ctx);
-       if (!dict) return -1;
+       fr_dict_attr_t const * const *bin;
+       fr_dict_attr_t **this;
 
        /*
-        *      Free the old dictionaries
+        *      Setup fields in the child
         */
-       if (*out == fr_dict_internal) fr_dict_internal = dict;
-       TALLOC_FREE(*out);
+       child->parent = parent;
+       child->depth = parent->depth + 1;
 
-       /*
-        *      Remove this at some point...
-        */
-       if (!fr_dict_internal) fr_dict_internal = dict;
+       VERIFY_DA(child);
 
        /*
-        *      Magic dictionary root attribute
+        *      We only allocate the pointer array *if* the parent has children.
         */
-       dict_root_set(dict, name, 0);
+       if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
+       if (!parent->children) return -1;
 
        /*
-        *      Add cast attributes.  We do it this way,
-        *      so cast attributes get added automatically for new types.
-        *
-        *      We manually add the attributes to the dictionary, and bypass
-        *      fr_dict_attr_add(), because we know what we're doing, and
-        *      that function does too many checks.
+        *      Treat the array as a hash of 255 bins, with attributes
+        *      sorted into bins using num % 255.
+        *
+        *      Although the various protocols may define numbers higher than 255:
+        *
+        *      RADIUS/DHCPv4     - 1-255
+        *      Diameter/Internal - 1-4294967295
+        *      DHCPv6            - 1-65535
+        *
+        *      In reality very few will ever use attribute numbers > 500, so for
+        *      the majority of lookups we get O(1) performance.
+        *
+        *      Attributes are inserted into the bin in order of their attribute
+        *      numbers to allow slightly more efficient lookups.
         */
-       if (!defined_cast_types) {
-               FR_NAME_NUMBER const    *p;
-               fr_dict_attr_flags_t    flags;
-               char                    *type_name;
-
-               memset(&flags, 0, sizeof(flags));
-
-               flags.internal = 1;
-
-               for (p = dict_attr_types; p->name; p++) {
-                       fr_dict_attr_t *n;
-
-                       type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
-
-                       n = dict_attr_alloc(dict->pool, dict->root, type_name,
-                                           FR_CAST_BASE + p->number, p->number, &flags);
-                       if (!n) {
-                       error:
-                               talloc_free(dict);
-                               return -1;
-                       }
+       bin = &parent->children[child->attr & 0xff];
+       for (;;) {
+               bool child_is_struct = false;
+               bool bin_is_struct = false;
 
-                       if (!fr_hash_table_insert(dict->attributes_by_name, n)) goto error;
+               if (!*bin) break;
 
-                       /*
-                        *      Set up parenting for the attribute.
-                        */
-                       if (dict_attr_child_add(dict->root, n) < 0) goto error;
+               /*
+                *      Workaround for vendors that overload the RFC space.
+                *      Structural attributes always take priority.
+                */
+               switch (child->type) {
+               case FR_TYPE_STRUCTURAL:
+                       child_is_struct = true;
+                       break;
 
-                       talloc_free(type_name);
+               default:
+                       break;
                }
-               defined_cast_types = true;
-       }
-
-       if (dict_from_file(dict, dir, fn, NULL, 0) < 0) goto error;
-
-       /*
-        *      Resolve any VALUE aliases (enums) that were defined
-        *      before the attributes they reference.
-        */
-       if (dict->enum_fixup) {
-               fr_dict_attr_t const *da;
-               dict_enum_fixup_t *this, *next;
-
-               for (this = dict->enum_fixup; this != NULL; this = next) {
-                       fr_value_box_t  value;
-                       fr_type_t       type;
-
-                       next = this->next;
-                       da = fr_dict_attr_by_name(dict, this->attribute);
-                       if (!da) {
-                               fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
-                                                  this->attribute, this->alias);
-                               goto error;
-                       }
-                       type = da->type;
-
-                       if (fr_value_box_from_str(this, &value, &type, NULL,
-                                                 this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
-                               fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
-                               goto error;
-                       }
 
-                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+               switch ((*bin)->type) {
+               case FR_TYPE_STRUCTURAL:
+                       bin_is_struct = true;
+                       break;
 
-                       /*
-                        *      Just so we don't lose track of things.
-                        */
-                       dict->enum_fixup = next;
+               default:
+                       break;
                }
-       }
 
-       /*
-        *      Walk over all of the hash tables to ensure they're
-        *      initialized.  We do this because the threads may perform
-        *      lookups, and we don't want multi-threaded re-ordering
-        *      of the table entries.  That would be bad.
-        */
-       fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
-       fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
+               if (child_is_struct && !bin_is_struct) break;
+               else if (fr_dict_vendor_num_by_da(child) <= fr_dict_vendor_num_by_da(*bin)) break;      /* Prioritise RFC attributes */
+               else if (child->attr <= (*bin)->attr) break;
 
-       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
-       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+               bin = &(*bin)->next;
+       }
 
-       *out = dict;
+       memcpy(&this, &bin, sizeof(this));
+       child->next = *this;
+       *this = child;
 
        return 0;
 }
 
-/** (Re-)Initialize the special internal dictionary
- *
- * This dictionary has additional programatically generated attributes added to it.
+/** Add an attribute to the name table for the dictionary.
  *
- * @param[in] ctx              to allocate dictionary in.
- * @param[out] out             Where to write pointer to the internal dictionary.
- * @param[in] dir              dictionary is located in.
- * @param[in] internal_name    name of the internal dictionary dir (may be NULL).
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] parent           to add attribute under.
+ * @param[in] name             of the attribute.
+ * @param[in] attr             number.
+ * @param[in] type             of attribute.
+ * @param[in] flags            to set in the attribute.
  * @return
  *     - 0 on success.
  *     - -1 on failure.
  */
-int fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *internal_name)
+static int dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t *da)
 {
-       fr_dict_t               *dict = fr_dict_internal;
-       char                    *dict_dir;
-       char                    *tmp;
-       FR_NAME_NUMBER const    *p;
-       fr_dict_attr_flags_t    flags = { .internal = true };
-       char                    *type_name;
-
-       memcpy(&tmp, &dir, sizeof(tmp));
-       dict_dir = internal_name ? talloc_asprintf(NULL, "%s%c%s", dir, FR_DIR_SEP, internal_name) : tmp;
-
-       if ((!protocol_by_name || !protocol_by_num) && (dict_global_init(ctx) < 0)) return -1;
+       /*
+        *      Insert the attribute, only if it's not a duplicate.
+        */
+       if (!fr_hash_table_insert(dict->attributes_by_name, da)) {
+               fr_dict_attr_t *a;
 
-       if (!dict) {
-               dict = dict_alloc(ctx);
-               if (!dict) {
-               error:
-                       if (!fr_dict_internal) talloc_free(dict);
-                       if (internal_name) talloc_free(dict_dir);
-                       return -1;
+               /*
+                *      If the attribute has identical number, then
+                *      error out.  We don't allow duplicate attribute
+                *      definitions.
+                */
+               a = fr_hash_table_finddata(dict->attributes_by_name, da);
+               if (a && (strcasecmp(a->name, da->name) == 0)) {
+                       if ((a->attr != da->attr) || (a->parent != da->parent)) {
+                               fr_strerror_printf("Duplicate attribute name");
+                       error:
+                               return -1;
+                       }
                }
 
                /*
-                *      Set the root name of the dictionary
+                *      Otherwise the attribute has been redefined later
+                *      in the dictionary.
+                *
+                *      The original fr_dict_attr_t remains in the
+                *      dictionary but entry in the name hash table is
+                *      updated to point to the new definition.
                 */
-               dict_root_set(dict, "internal", 0);
-       } else {
-               if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) {
-                       if (internal_name) talloc_free(dict_dir);
-                       return 0;
+               if (!fr_hash_table_replace(dict->attributes_by_name, da)) {
+                       fr_strerror_printf("Internal error storing attribute");
+                       goto error;
                }
        }
+
        /*
-        *      Add cast attributes.  We do it this way,
-        *      so cast attributes get added automatically for new types.
+        *      Insert copies of the attribute into the
+        *      polymorphic attribute table.
         *
-        *      We manually add the attributes to the dictionary, and bypass
-        *      fr_dict_attr_add(), because we know what we're doing, and
-        *      that function does too many checks.
+        *      This allows an abstract attribute type
+        *      like combo IP to be resolved to a
+        *      concrete one later.
         */
-       for (p = dict_attr_types; p->name; p++) {
-               fr_dict_attr_t *n;
+       switch (da->type) {
+       case FR_TYPE_COMBO_IP_ADDR:
+       {
+               fr_dict_attr_t *v4, *v6;
 
-               type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+               v4 = dict_attr_acopy(dict->pool, da);
+               if (!v4) goto error;
+               v4->type = FR_TYPE_IPV4_ADDR;
 
-               n = dict_attr_alloc(dict->pool, dict->root, type_name,
-                                   FR_CAST_BASE + p->number, p->number, &flags);
-               if (!n) goto error;
+               v6 = dict_attr_acopy(dict->pool, da);
+               if (!v6) goto error;
+               v6->type = FR_TYPE_IPV6_ADDR;
 
-               if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
-                       fr_strerror_printf("Failed inserting \"%s\" into internal dictionary", type_name);
+               if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
+                       fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
                        goto error;
                }
 
-               /*
-                *      Set up parenting for the attribute.
-                */
-               if (dict_attr_child_add(dict->root, n) < 0) goto error;
-
-               talloc_free(type_name);
+               if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
+                       fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
+                       goto error;
+               }
        }
 
-       if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
+       case FR_TYPE_COMBO_IP_PREFIX:
+       {
+               fr_dict_attr_t *v4, *v6;
 
-       *out = dict;
-       if (!fr_dict_internal) fr_dict_internal = dict;
+               v4 = dict_attr_acopy(dict->pool, da);
+               if (!v4) goto error;
+               v4->type = FR_TYPE_IPV4_PREFIX;
+
+               v6 = dict_attr_acopy(dict->pool, da);
+               if (!v6) goto error;
+               v6->type = FR_TYPE_IPV6_PREFIX;
+
+               if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
+                       fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
+                       goto error;
+               }
+
+               if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
+                       fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
+                       goto error;
+               }
+       }
+       default:
+               break;
+       }
 
        return 0;
 }
 
-/** (Re)-initialize a protocol dictionary
- *
- * Initialize the directory, then fix the attr member of all attributes.
- *
- * First dictionary initialised will be set as the default internal dictionary.
+
+/** Add an reference to the dictionary
  *
- * @param[in] ctx              to allocate the dictionary from.
- * @param[out] out             Where to write a pointer to the new dictionary.  Will free existing
- *                             dictionary if files have changed and *out is not NULL.
- * @param[in] base_dir         containing all the protocol directories.
- * @param[in] proto_name       that we're loading the dictionary for.
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] parent           to add attribute under.
+ * @param[in] name             of the attribute.
+ * @param[in] attr             number.
+ * @param[in] type             of attribute.
+ * @param[in] flags            to set in the attribute.
+ * @param[in] ref              The attribute we're referencing.  May be in a foreign
+ *                             dictionary.
  * @return
  *     - 0 on success.
  *     - -1 on failure.
  */
-int fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
-                               char const *base_dir, char const *proto_name)
+static int dict_attr_ref_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
+                            char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags,
+                            fr_dict_attr_t const *ref)
 {
-       fr_dict_t       *dict;
-       char            *dir;
-       char            *proto_dir;
-       char            *p;
-
-       if (!protocol_by_name || !protocol_by_num) {
-               fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
-               return -1;
-       }
+       fr_dict_attr_t          *n;
+       fr_dict_attr_t          *mutable;
+       fr_dict_attr_flags_t    our_flags = *flags;
 
        /*
-        *      Increment the reference count if the dictionary
-        *      has already been loaded.
+        *      Check it's valid
         */
-       if (!*out) {
-               *out = fr_dict_by_protocol_name(proto_name);
-               if (*out) {
-                        talloc_increase_ref_count(*out);
-                        return 0;
-               }
-       }
+       if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
 
        /*
-        *      Replace '_' with '/'
+        *      Check we're not creating a direct loop
         */
-       proto_dir = talloc_strdup(ctx, proto_name);
-       for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
+       if (ref->flags.is_reference) {
+               fr_dict_attr_ref_t const *to_ref = talloc_get_type_abort_const(ref, fr_dict_attr_ref_t);
 
-       dir = talloc_asprintf(proto_dir, "%s%c%s", base_dir, FR_DIR_SEP, proto_dir);
-       if (!*out) {
-               dict = dict_alloc(ctx);
-               if (!dict) {
-               error:
-                       talloc_free(proto_dir);
+               if (to_ref->to == ref) {
+                       fr_strerror_printf("Circular reference between \"%s\" and \"%s\"", name, ref->name);
                        return -1;
                }
-       } else {
-               dict = *out;
-               if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) return 0;
        }
 
-       dict->enum_fixup = NULL;        /* just to be safe. */
-
-       if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
-
-       talloc_free(proto_dir);
-
        /*
-        *      Resolve any VALUE aliases (enums) that were defined
-        *      before the attributes they reference.
+        *      Check the referenced attribute is a root
+        *      or a TLV attribute.
         */
-       if (dict->enum_fixup) {
-               fr_dict_attr_t const *da;
-               dict_enum_fixup_t *this, *next;
-
-               for (this = dict->enum_fixup; this != NULL; this = next) {
-                       fr_value_box_t  value;
-                       fr_type_t       type;
-
-                       next = this->next;
-                       da = fr_dict_attr_by_name(dict, this->attribute);
-                       if (!da) {
-                               fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
-                                                  this->attribute, this->alias);
-                               goto error;
-                       }
-                       type = da->type;
-
-                       if (fr_value_box_from_str(this, &value, &type, NULL,
-                                                 this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
-                               fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
-                               goto error;
-                       }
+       if (!ref->flags.is_root && (ref->type != FR_TYPE_TLV)) {
+               fr_strerror_printf("Referenced attribute \"%s\" is not a TLV", ref->name);
+               return -1;
+       }
 
-                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+       n = dict_attr_ref_alloc(dict->pool, parent, name, attr, type, &our_flags, ref);
+       if (!n) return -1;
 
-                       /*
-                        *      Just so we don't lose track of things.
-                        */
-                       dict->enum_fixup = next;
-               }
+       if (dict_attr_add_by_name(dict, n) < 0) {
+       error:
+               talloc_free(n);
+               return -1;
        }
 
        /*
-        *      Walk over all of the hash tables to ensure they're
-        *      initialized.  We do this because the threads may perform
-        *      lookups, and we don't want multi-threaded re-ordering
-        *      of the table entries.  That would be bad.
+        *      Setup parenting for the attribute
         */
-       fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
-       fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
-
-       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
-       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
+       memcpy(&mutable, &parent, sizeof(mutable));
 
-       *out = dict;
+       /*
+        *      Add in by number
+        */
+       if (dict_attr_child_add(mutable, n) < 0) goto error;
 
        return 0;
 }
 
-/** Read supplementary attribute definitions into an existing dictionary
- *
- * @param[in] dict     Existing dictionary.
- * @param[in] dir      dictionary is located in.
- * @param[in] filename of the dictionary.
- */
-int fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename)
-{
-       INTERNAL_IF_NULL(dict);
-
-       if (!dict->attributes_by_name) {
-               fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", __FUNCTION__);
-               return -1;
-       }
-
-       return dict_from_file(dict, dir, filename, NULL, 0);
-}
-
-/** Add a protocol to the global protocol table
- *
- * Inserts a protocol into the global protocol table.  Uses the root attributes
- * of the dictionary for comparisons.
+/** Add an attribute to the dictionary
  *
- * @param[in] dict of protocol we're inserting.
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] parent           to add attribute under.
+ * @param[in] name             of the attribute.
+ * @param[in] attr             number.
+ * @param[in] type             of attribute.
+ * @param[in] flags            to set in the attribute.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-static int dict_protocol_add(fr_dict_t *dict)
+int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
+                    char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags)
 {
-       if (!dict->root) return -1;     /* Should always have root */
-
-       if (!fr_hash_table_insert(protocol_by_name, dict)) {
-               fr_dict_t *old_proto;
-
-               old_proto = fr_hash_table_finddata(protocol_by_name, dict);
-               if (!old_proto) {
-                       fr_strerror_printf("%s: Failed inserting protocol name %s", __FUNCTION__, dict->root->name);
-                       return -1;
-               }
+       fr_dict_attr_t          *n;
+       fr_dict_attr_t          *mutable;
+       fr_dict_attr_flags_t    our_flags = *flags;
 
-               if ((strcmp(old_proto->root->name, dict->root->name) == 0) &&
-                   (old_proto->root->name == dict->root->name)) {
-                       fr_strerror_printf("%s: Duplicate protocol name %s", __FUNCTION__, dict->root->name);
-                       return -1;
-               }
+       /*
+        *      Check it's valid
+        */
+       if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
 
-               return 0;
-       }
+       n = dict_attr_alloc(dict->pool, parent, name, attr, type, &our_flags);
+       if (!n) return -1;
 
-       if (!fr_hash_table_insert(protocol_by_num, dict)) {
-               fr_strerror_printf("%s: Duplicate protocol number %i", __FUNCTION__, dict->root->attr);
+       if (dict_attr_add_by_name(dict, n) < 0) {
+       error:
+               talloc_free(n);
                return -1;
        }
 
+       /*
+        *      Setup parenting for the attribute
+        */
+       memcpy(&mutable, &parent, sizeof(mutable));
+
+       /*
+        *      Add in by number
+        */
+       if (dict_attr_child_add(mutable, n) < 0) goto error;
+
        return 0;
 }
 
-/** Add a vendor to the dictionary
+/** Add a value alias
  *
- * Inserts a vendor entry into the vendor hash table.  This must be done before adding
- * attributes under a VSA.
+ * Aliases are textual (string) aliases for a given value.
  *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] name             of the vendor.
- * @param[in] num              Vendor's Private Enterprise Number.
+ * Value aliases are not limited to integers, and may be added for any non-structural
+ * attribute type.
+ *
+ * @param[in] da               to add enumeration value to.
+ * @param[in] alias            Name of value alias.
+ * @param[in] value            to associate with alias.
+ * @param[in] coerce           if the type of the value does not match the
+ *                             type of the da, attempt to cast it to match
+ *                             the type of the da.  If this is false and there's
+ *                             a type mismatch, we fail.
+ *                             We also fail if the value cannot be coerced to
+ *                             the attribute type.
+ * @param[in] takes_precedence This alias should take precedence over previous
+ *                             aliases for the same value, when resolving value
+ *                             to alias.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-static int dict_vendor_add(fr_dict_t *dict, char const *name, unsigned int num)
+int fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
+                          fr_value_box_t const *value,
+                          bool coerce, bool takes_precedence)
 {
-       INTERNAL_IF_NULL(dict);
        size_t                  len;
-       fr_dict_vendor_t        *vendor;
+       fr_dict_t               *dict;
+       fr_dict_enum_t          *enumv = NULL;
+       fr_value_box_t          *enum_value = NULL;
 
-       len = strlen(name);
-       if (len >= FR_DICT_VENDOR_MAX_NAME_LEN) {
-               fr_strerror_printf("%s: Vendor name too long", __FUNCTION__);
+       if (!da) {
+               fr_strerror_printf("%s: Dictionary attribute not specified", __FUNCTION__);
                return -1;
        }
 
-#ifdef HAVE_TALLOC_POOLED_OBJECT
-       vendor = talloc_pooled_object(dict, fr_dict_vendor_t, 1, strlen(name) + 1);
-       memset(vendor, 0, sizeof(*vendor));
-#else
-       vendor = talloc_zero(dict, fr_dict_vendor_t);
-#endif
+       if (!*alias) {
+               fr_strerror_printf("%s: Empty names are not permitted", __FUNCTION__);
+               return -1;
+       }
 
-       vendor->name = talloc_typed_strdup(vendor, name);
-       if (!vendor->name) {
-               talloc_free(vendor);
-               fr_strerror_printf("Out of memory");
+       len = strlen(alias);
+       if (len >= FR_DICT_ENUM_MAX_NAME_LEN) {
+               fr_strerror_printf("%s: Value name too long", __FUNCTION__);
                return -1;
        }
-       vendor->vendorpec = num;
-       vendor->type = vendor->length = 1; /* defaults */
 
-       if (!fr_hash_table_insert(dict->vendors_by_name, vendor)) {
-               fr_dict_vendor_t *old_vendor;
+       dict = fr_dict_by_da(da);
 
-               old_vendor = fr_hash_table_finddata(dict->vendors_by_name, vendor);
-               if (!old_vendor) {
-                       fr_strerror_printf("%s: Failed inserting vendor name %s", __FUNCTION__, name);
-                       return -1;
-               }
-               if ((strcmp(old_vendor->name, vendor->name) == 0) && (old_vendor->vendorpec != vendor->vendorpec)) {
-                       fr_strerror_printf("%s: Duplicate vendor name %s", __FUNCTION__, name);
+       enumv = talloc_zero(dict->pool, fr_dict_enum_t);
+       if (!enumv) {
+               fr_strerror_printf("%s: Out of memory", __FUNCTION__);
+               return -1;
+       }
+       enumv->alias = talloc_typed_strdup(enumv, alias);
+       enum_value = fr_value_box_alloc(enumv, da->type, NULL, false);
+
+       if (da->type != value->type) {
+               if (!coerce) {
+                       fr_strerror_printf("%s: Type mismatch between attribute (%s) and enum (%s)",
+                                          __FUNCTION__,
+                                          fr_int2str(dict_attr_types, da->type, "<INVALID>"),
+                                          fr_int2str(dict_attr_types, value->type, "<INVALID>"));
                        return -1;
                }
 
-               /*
-                *      Already inserted.  Discard the duplicate entry.
-                */
-               talloc_free(vendor);
+               if (fr_value_box_cast(enumv, enum_value, da->type, NULL, value) < 0) {
+                       fr_strerror_printf_push("%s: Failed coercing enum type (%s) to attribute type (%s)",
+                                               __FUNCTION__,
+                                               fr_int2str(dict_attr_types, value->type, "<INVALID>"),
+                                               fr_int2str(dict_attr_types, da->type, "<INVALID>"));
 
-               return 0;
+                       return -1;
+               }
+       } else {
+               if (fr_value_box_copy(enum_value, enum_value, value) < 0) {
+                       fr_strerror_printf_push("%s: Failed copying value into enum", __FUNCTION__);
+                       return -1;
+               }
        }
 
+       enumv->value = enum_value;
+       enumv->da = da;
+
        /*
-        *      Insert the SAME pointer (not free'd when this table is
-        *      deleted), into another table.
-        *
-        *      We want this behaviour because we want OLD names for
-        *      the attributes to be read from the configuration
-        *      files, but when we're printing them, (and looking up
-        *      by value) we want to use the NEW name.
+        *      Add the value into the dictionary.
         */
-       if (!fr_hash_table_replace(dict->vendors_by_num, vendor)) {
-               fr_strerror_printf("%s: Failed inserting vendor %s", __FUNCTION__, name);
-               return -1;
-       }
+       {
+               fr_dict_attr_t *tmp;
+               memcpy(&tmp, &enumv, sizeof(tmp));
 
-       return 0;
-}
+               if (!fr_hash_table_insert(dict->values_by_alias, tmp)) {
+                       fr_dict_enum_t *old;
 
-/** Add a child to a parent.
- *
- * @param[in] parent   we're adding a child to.
- * @param[in] child    to add to parent.
- * @return
- *     - 0 on success.
- *     - -1 on failure (memory allocation error).
- */
-static inline int dict_attr_child_add(fr_dict_attr_t *parent, fr_dict_attr_t *child)
-{
-       fr_dict_attr_t const * const *bin;
-       fr_dict_attr_t **this;
+                       /*
+                        *      Suppress duplicates with the same
+                        *      name and value.  There are lots in
+                        *      dictionary.ascend.
+                        */
+                       old = fr_dict_enum_by_alias(dict, da, alias);
+                       if (!fr_cond_assert(old)) return -1;
 
-       /*
-        *      Setup fields in the child
-        */
-       child->parent = parent;
-       child->depth = parent->depth + 1;
+                       if (fr_value_box_cmp(old->value, enumv->value) == 0) {
+                               talloc_free(enumv);
+                               return 0;
+                       }
 
-       VERIFY_DA(child);
+                       fr_strerror_printf("Duplicate VALUE alias \"%s\" for attribute \"%s\". "
+                                          "Old value was \"%pV\", new value was \"%pV\"", alias, da->name,
+                                          old->value, enumv->value);
+                       talloc_free(enumv);
+                       return -1;
+               }
+       }
 
        /*
-        *      We only allocate the pointer array *if* the parent has children.
+        *      There are multiple VALUE's, keyed by attribute, so we
+        *      take care of that here.
         */
-       if (!parent->children) parent->children = talloc_zero_array(parent, fr_dict_attr_t const *, UINT8_MAX + 1);
-       if (!parent->children) return -1;
+       if (takes_precedence) {
+               if (!fr_hash_table_replace(dict->values_by_da, enumv)) {
+                       fr_strerror_printf("%s: Failed inserting value %s", __FUNCTION__, alias);
+                       return -1;
+               }
+       } else {
+               (void) fr_hash_table_insert(dict->values_by_da, enumv);
+       }
 
        /*
-        *      Treat the array as a hash of 255 bins, with attributes
-        *      sorted into bins using num % 255.
-        *
-        *      Although the various protocols may define numbers higher than 255:
-        *
-        *      RADIUS/DHCPv4     - 1-255
-        *      Diameter/Internal - 1-4294967295
-        *      DHCPv6            - 1-65535
-        *
-        *      In reality very few will ever use attribute numbers > 500, so for
-        *      the majority of lookups we get O(1) performance.
-        *
-        *      Attributes are inserted into the bin in order of their attribute
-        *      numbers to allow slightly more efficient lookups.
+        *      Mark the attribute up as having an enumv
         */
-       bin = &parent->children[child->attr & 0xff];
-       for (;;) {
-               bool child_is_struct = false;
-               bool bin_is_struct = false;
-
-               if (!*bin) break;
-
-               /*
-                *      Workaround for vendors that overload the RFC space.
-                *      Structural attributes always take priority.
-                */
-               switch (child->type) {
-               case FR_TYPE_STRUCTURAL:
-                       child_is_struct = true;
-                       break;
+       {
+               fr_dict_attr_t *mutable;
 
-               default:
-                       break;
-               }
+               memcpy(&mutable, &da, sizeof(mutable));
 
-               switch ((*bin)->type) {
-               case FR_TYPE_STRUCTURAL:
-                       bin_is_struct = true;
-                       break;
+               mutable->flags.has_value = 1;
+       }
 
-               default:
-                       break;
-               }
+       return 0;
+}
 
-               if (child_is_struct && !bin_is_struct) break;
-               else if (fr_dict_vendor_num_by_da(child) <= fr_dict_vendor_num_by_da(*bin)) break;      /* Prioritise RFC attributes */
-               else if (child->attr <= (*bin)->attr) break;
+/** Copy a known or unknown attribute to produce an unknown attribute
+ *
+ * Will copy the complete hierarchy down to the first known attribute.
+ */
+fr_dict_attr_t *fr_dict_unknown_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
+{
+       fr_dict_attr_t *n, *new_parent = NULL;
+       fr_dict_attr_t const *parent;
 
-               bin = &(*bin)->next;
+       if (da->parent->flags.is_unknown) {
+               new_parent = fr_dict_unknown_acopy(ctx, da->parent);
+               parent = new_parent;
+       } else {
+               parent = da->parent;
        }
 
-       memcpy(&this, &bin, sizeof(this));
-       child->next = *this;
-       *this = child;
+       n = dict_attr_alloc(ctx, parent, da->name, da->attr, da->type, &da->flags);
+       n->parent = parent;
+       n->depth = da->depth;
 
-       return 0;
+       /*
+        *      Inverted tallloc hierarchy.
+        */
+       if (new_parent) talloc_steal(n, parent);
+
+       return n;
 }
 
-/** Add an attribute to the name table for the dictionary.
+/** Converts an unknown to a known by adding it to the internal dictionaries.
+ *
+ * Does not free old #fr_dict_attr_t, that is left up to the caller.
  *
  * @param[in] dict             of protocol context we're operating in.
  *                             If NULL the internal dictionary will be used.
- * @param[in] parent           to add attribute under.
- * @param[in] name             of the attribute.
- * @param[in] attr             number.
- * @param[in] type             of attribute.
- * @param[in] flags            to set in the attribute.
+ * @param[in] old              unknown attribute to add.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - Existing #fr_dict_attr_t if old was found in a dictionary.
+ *     - A new entry representing old.
  */
-static int dict_attr_add_by_name(fr_dict_t *dict, fr_dict_attr_t *da)
+fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const *old)
 {
-       /*
-        *      Insert the attribute, only if it's not a duplicate.
-        */
-       if (!fr_hash_table_insert(dict->attributes_by_name, da)) {
-               fr_dict_attr_t *a;
+       fr_dict_attr_t const *da;
+       fr_dict_attr_t const *parent;
+       fr_dict_attr_flags_t flags;
 
-               /*
-                *      If the attribute has identical number, then
-                *      error out.  We don't allow duplicate attribute
-                *      definitions.
-                */
-               a = fr_hash_table_finddata(dict->attributes_by_name, da);
-               if (a && (strcasecmp(a->name, da->name) == 0)) {
-                       if ((a->attr != da->attr) || (a->parent != da->parent)) {
-                               fr_strerror_printf("Duplicate attribute name");
-                       error:
-                               return -1;
-                       }
-               }
+       if (!old) return NULL;
 
-               /*
-                *      Otherwise the attribute has been redefined later
-                *      in the dictionary.
-                *
-                *      The original fr_dict_attr_t remains in the
-                *      dictionary but entry in the name hash table is
-                *      updated to point to the new definition.
-                */
-               if (!fr_hash_table_replace(dict->attributes_by_name, da)) {
-                       fr_strerror_printf("Internal error storing attribute");
-                       goto error;
-               }
-       }
+       da = fr_dict_attr_by_name(dict, old->name);
+       if (da) return da;
 
        /*
-        *      Insert copies of the attribute into the
-        *      polymorphic attribute table.
-        *
-        *      This allows an abstract attribute type
-        *      like combo IP to be resolved to a
-        *      concrete one later.
+        *      Define the complete unknown hierarchy
         */
-       switch (da->type) {
-       case FR_TYPE_COMBO_IP_ADDR:
-       {
-               fr_dict_attr_t *v4, *v6;
-
-               v4 = dict_attr_acopy(dict->pool, da);
-               if (!v4) goto error;
-               v4->type = FR_TYPE_IPV4_ADDR;
-
-               v6 = dict_attr_acopy(dict->pool, da);
-               if (!v6) goto error;
-               v6->type = FR_TYPE_IPV6_ADDR;
-
-               if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
-                       fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
-                       goto error;
-               }
-
-               if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
-                       fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
-                       goto error;
+       if (old->parent && old->parent->flags.is_unknown) {
+               parent = fr_dict_unknown_add(dict, old->parent);
+               if (!parent) {
+                       fr_strerror_printf_push("Failed adding parent \"%s\"", old->parent->name);
+                       return NULL;
                }
+       } else {
+               parent = old->parent;
        }
 
-       case FR_TYPE_COMBO_IP_PREFIX:
-       {
-               fr_dict_attr_t *v4, *v6;
-
-               v4 = dict_attr_acopy(dict->pool, da);
-               if (!v4) goto error;
-               v4->type = FR_TYPE_IPV4_PREFIX;
-
-               v6 = dict_attr_acopy(dict->pool, da);
-               if (!v6) goto error;
-               v6->type = FR_TYPE_IPV6_PREFIX;
+       memcpy(&flags, &old->flags, sizeof(flags));
+       flags.is_unknown = false;
+       flags.is_raw = true;
 
-               if (!fr_hash_table_replace(dict->attributes_combo, v4)) {
-                       fr_strerror_printf("Failed inserting IPv4 version of combo attribute");
-                       goto error;
-               }
+       /*
+        *      If this is a vendor, we skip most of the sanity
+        *      checks and add it to the vendor hash, and add it
+        *      as a child attribute to the Vendor-Specific
+        *      container.
+        */
+       if (old->type == FR_TYPE_VENDOR) {
+               fr_dict_attr_t *mutable, *n;
 
-               if (!fr_hash_table_replace(dict->attributes_combo, v6)) {
-                       fr_strerror_printf("Failed inserting IPv6 version of combo attribute");
-                       goto error;
-               }
-       }
-       default:
-               break;
-       }
+               if (dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
 
-       return 0;
-}
+               n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
 
+               /*
+                *      Setup parenting for the attribute
+                */
+               memcpy(&mutable, &old->parent, sizeof(mutable));
+               if (dict_attr_child_add(mutable, n) < 0) return NULL;
 
-/** Add an reference to the dictionary
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] parent           to add attribute under.
- * @param[in] name             of the attribute.
- * @param[in] attr             number.
- * @param[in] type             of attribute.
- * @param[in] flags            to set in the attribute.
- * @param[in] ref              The attribute we're referencing.  May be in a foreign
- *                             dictionary.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int dict_attr_ref_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
-                            char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags,
-                            fr_dict_attr_t const *ref)
-{
-       fr_dict_attr_t          *n;
-       fr_dict_attr_t          *mutable;
-       fr_dict_attr_flags_t    our_flags = *flags;
+               return n;
+       }
 
        /*
-        *      Check it's valid
+        *      Look up the attribute by number.  If it doesn't exist,
+        *      add it both by name and by number.  If it does exist,
+        *      add it only by name.
         */
-       if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
+       da = fr_dict_attr_child_by_num(parent, old->attr);
+       if (da) {
+               fr_dict_attr_t *n;
 
-       /*
-        *      Check we're not creating a direct loop
-        */
-       if (ref->flags.is_reference) {
-               fr_dict_attr_ref_t const *to_ref = talloc_get_type_abort_const(ref, fr_dict_attr_ref_t);
+               n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+               if (!n) return NULL;
 
-               if (to_ref->to == ref) {
-                       fr_strerror_printf("Circular reference between \"%s\" and \"%s\"", name, ref->name);
-                       return -1;
+               /*
+                *      Add the unknown by NAME.  e.g. if the admin does "Attr-26", we want
+                *      to return "Attr-26", and NOT "Vendor-Specific".  The rest of the server
+                *      is responsible for converting "Attr-26 = 0x..." to an actual attribute,
+                *      if it so desires.
+                */
+               if (dict_attr_add_by_name(dict, n) < 0) {
+                       talloc_free(n);
+                       return NULL;
                }
-       }
-
-       /*
-        *      Check the referenced attribute is a root
-        *      or a TLV attribute.
-        */
-       if (!ref->flags.is_root && (ref->type != FR_TYPE_TLV)) {
-               fr_strerror_printf("Referenced attribute \"%s\" is not a TLV", ref->name);
-               return -1;
-       }
-
-       n = dict_attr_ref_alloc(dict->pool, parent, name, attr, type, &our_flags, ref);
-       if (!n) return -1;
 
-       if (dict_attr_add_by_name(dict, n) < 0) {
-       error:
-               talloc_free(n);
-               return -1;
+               return n;
        }
 
        /*
-        *      Setup parenting for the attribute
+        *      Add the attribute by both name and number.
         */
-       memcpy(&mutable, &parent, sizeof(mutable));
+       if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, &flags) < 0) return NULL;
 
        /*
-        *      Add in by number
+        *      For paranoia, return it by name.
         */
-       if (dict_attr_child_add(mutable, n) < 0) goto error;
-
-       return 0;
+       return fr_dict_attr_by_name(dict, old->name);
 }
 
-/** Add an attribute to the dictionary
+/** Free dynamically allocated (unknown attributes)
  *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] parent           to add attribute under.
- * @param[in] name             of the attribute.
- * @param[in] attr             number.
- * @param[in] type             of attribute.
- * @param[in] flags            to set in the attribute.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
+ * If the da was dynamically allocated it will be freed, else the function
+ * will return without doing anything.
+ *
+ * @param[in] da to free.
  */
-int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent,
-                    char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags)
+void fr_dict_unknown_free(fr_dict_attr_t const **da)
 {
-       fr_dict_attr_t          *n;
-       fr_dict_attr_t          *mutable;
-       fr_dict_attr_flags_t    our_flags = *flags;
-
-       /*
-        *      Check it's valid
-        */
-       if (!dict_attr_fields_valid(dict, parent, name, &attr, type, &our_flags)) return -1;
+       fr_dict_attr_t **tmp;
 
-       n = dict_attr_alloc(dict->pool, parent, name, attr, type, &our_flags);
-       if (!n) return -1;
+       if (!da || !*da) return;
 
-       if (dict_attr_add_by_name(dict, n) < 0) {
-       error:
-               talloc_free(n);
-               return -1;
+       /* Don't free real DAs */
+       if (!(*da)->flags.is_unknown) {
+               return;
        }
 
-       /*
-        *      Setup parenting for the attribute
-        */
-       memcpy(&mutable, &parent, sizeof(mutable));
-
-       /*
-        *      Add in by number
-        */
-       if (dict_attr_child_add(mutable, n) < 0) goto error;
+       memcpy(&tmp, &da, sizeof(*tmp));
+       talloc_free(*tmp);
 
-       return 0;
+       *tmp = NULL;
 }
 
-/** Add a value alias
+/** Build an unknown vendor, parented by a VSA or EVS attribute
  *
- * Aliases are textual (string) aliases for a given value.
+ * This allows us to complete the path back to the dictionary root in the case
+ * of unknown attributes with unknown vendors.
  *
- * Value aliases are not limited to integers, and may be added for any non-structural
- * attribute type.
+ * @note Will return known vendors attributes where possible.  Do not free directly,
+ *     use #fr_dict_unknown_free.
  *
- * @param[in] da               to add enumeration value to.
- * @param[in] alias            Name of value alias.
- * @param[in] value            to associate with alias.
- * @param[in] coerce           if the type of the value does not match the
- *                             type of the da, attempt to cast it to match
- *                             the type of the da.  If this is false and there's
- *                             a type mismatch, we fail.
- *                             We also fail if the value cannot be coerced to
- *                             the attribute type.
- * @param[in] takes_precedence This alias should take precedence over previous
- *                             aliases for the same value, when resolving value
- *                             to alias.
+ * @param[in] ctx to allocate the vendor attribute in.
+ * @param[out] out             Where to write point to new unknown dict attr
+ *                             representing the unknown vendor.
+ * @param[in] parent           of the vendor attribute, either an EVS or VSA attribute.
+ * @param[in] vendor           id.
  * @return
  *     - 0 on success.
  *     - -1 on failure.
  */
-int fr_dict_enum_add_alias(fr_dict_attr_t const *da, char const *alias,
-                          fr_value_box_t const *value,
-                          bool coerce, bool takes_precedence)
+int fr_dict_unknown_vendor_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+                                    fr_dict_attr_t const *parent, unsigned int vendor)
 {
-       size_t                  len;
-       fr_dict_t               *dict;
-       fr_dict_enum_t          *enumv = NULL;
-       fr_value_box_t          *enum_value = NULL;
+       fr_dict_attr_flags_t    flags = {
+                                       .is_unknown = true,
+                                       .is_raw = true,
+                                       .type_size = true,
+                                       .length = true
+                               };
 
-       if (!da) {
-               fr_strerror_printf("%s: Dictionary attribute not specified", __FUNCTION__);
+       if (!fr_cond_assert(parent)) {
+               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
                return -1;
        }
 
-       if (!*alias) {
-               fr_strerror_printf("%s: Empty names are not permitted", __FUNCTION__);
-               return -1;
-       }
+       *out = NULL;
 
-       len = strlen(alias);
-       if (len >= FR_DICT_ENUM_MAX_NAME_LEN) {
-               fr_strerror_printf("%s: Value name too long", __FUNCTION__);
-               return -1;
-       }
+       /*
+        *      Vendor attributes can occur under VSA or EVS attributes.
+        */
+       switch (parent->type) {
+       case FR_TYPE_VSA:
+       case FR_TYPE_EVS:
+               if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
 
-       dict = fr_dict_by_da(da);
+               *out = dict_attr_alloc(ctx, parent, NULL, vendor, FR_TYPE_VENDOR, &flags);
 
-       enumv = talloc_zero(dict->pool, fr_dict_enum_t);
-       if (!enumv) {
-               fr_strerror_printf("%s: Out of memory", __FUNCTION__);
+               return 0;
+
+       case FR_TYPE_VENDOR:
+               if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
+               fr_strerror_printf("Unknown vendor cannot be parented by another vendor");
                return -1;
-       }
-       enumv->alias = talloc_typed_strdup(enumv, alias);
-       enum_value = fr_value_box_alloc(enumv, da->type, NULL, false);
 
-       if (da->type != value->type) {
-               if (!coerce) {
-                       fr_strerror_printf("%s: Type mismatch between attribute (%s) and enum (%s)",
-                                          __FUNCTION__,
-                                          fr_int2str(dict_attr_types, da->type, "<INVALID>"),
-                                          fr_int2str(dict_attr_types, value->type, "<INVALID>"));
-                       return -1;
-               }
+       default:
+               fr_strerror_printf("Unknown vendors can only be parented by 'vsa' or 'evs' "
+                                  "attributes, not '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+               return -1;
+       }
+}
 
-               if (fr_value_box_cast(enumv, enum_value, da->type, NULL, value) < 0) {
-                       fr_strerror_printf_push("%s: Failed coercing enum type (%s) to attribute type (%s)",
-                                               __FUNCTION__,
-                                               fr_int2str(dict_attr_types, value->type, "<INVALID>"),
-                                               fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+/** Allocates an unknown attribute
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @note If vendor != 0, an unknown vendor (may) also be created, parented by
+ *     the correct EVS or VSA attribute. This is accessible via da->parent,
+ *     and will be use the unknown da as its talloc parent.
+ *
+ * @param[in] ctx              to allocate DA in.
+ * @param[in] parent           of the unknown attribute (may also be unknown).
+ * @param[in] attr             number.
+ * @param[in] vendor           number.
+ * @return 0 on success.
+ */
+fr_dict_attr_t const *fr_dict_unknown_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *parent,
+                                                  unsigned int vendor, unsigned int attr)
+{
+       fr_dict_attr_t const    *da;
+       fr_dict_attr_t          *n;
+       fr_dict_attr_t          *new_parent = NULL;
+       fr_dict_attr_flags_t    flags = {
+               .is_unknown     = true,
+               .is_raw         = true,
+       };
 
-                       return -1;
-               }
-       } else {
-               if (fr_value_box_copy(enum_value, enum_value, value) < 0) {
-                       fr_strerror_printf_push("%s: Failed copying value into enum", __FUNCTION__);
-                       return -1;
-               }
+       if (!fr_cond_assert(parent)) {
+               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+               return NULL;
        }
 
-       enumv->value = enum_value;
-       enumv->da = da;
-
        /*
-        *      Add the value into the dictionary.
+        *      If there's a vendor specified, we check to see
+        *      if the parent is a VSA or EVS, and if it is
+        *      we either lookup the vendor to get the correct
+        *      attribute, or bridge the gap in the tree, with an
+        *      unknown vendor.
+        *
+        *      We need to do the check, as the parent could be
+        *      a TLV, in which case the vendor should be known
+        *      and we don't need to modify the parent.
         */
-       {
-               fr_dict_attr_t *tmp;
-               memcpy(&tmp, &enumv, sizeof(tmp));
-
-               if (!fr_hash_table_insert(dict->values_by_alias, tmp)) {
-                       fr_dict_enum_t *old;
-
-                       /*
-                        *      Suppress duplicates with the same
-                        *      name and value.  There are lots in
-                        *      dictionary.ascend.
-                        */
-                       old = fr_dict_enum_by_alias(dict, da, alias);
-                       if (!fr_cond_assert(old)) return -1;
-
-                       if (fr_value_box_cmp(old->value, enumv->value) == 0) {
-                               talloc_free(enumv);
-                               return 0;
-                       }
-
-                       fr_strerror_printf("Duplicate VALUE alias \"%s\" for attribute \"%s\". "
-                                          "Old value was \"%pV\", new value was \"%pV\"", alias, da->name,
-                                          old->value, enumv->value);
-                       talloc_free(enumv);
-                       return -1;
+       if (vendor && ((parent->type == FR_TYPE_VSA) || (parent->type == FR_TYPE_EVS))) {
+               da = fr_dict_attr_child_by_num(parent, vendor);
+               if (!da) {
+                       if (fr_dict_unknown_vendor_afrom_num(ctx, &new_parent, parent, vendor) < 0) return NULL;
+                       da = new_parent;
                }
+               parent = da;
+
+       /*
+        *      Need to clone the unknown hierachy, as unknown
+        *      attributes must parent the complete heirachy,
+        *      and cannot share any parts with any other unknown
+        *      attributes.
+        */
+       } else if (parent->flags.is_unknown) {
+               new_parent = fr_dict_unknown_acopy(ctx, parent);
+               parent = new_parent;
        }
 
+       n = dict_attr_alloc(ctx, parent, NULL, attr, FR_TYPE_OCTETS, &flags);
+
        /*
-        *      There are multiple VALUE's, keyed by attribute, so we
-        *      take care of that here.
+        *      The config files may reference the unknown by name.
+        *      If so, use the pre-defined name instead of an unknown
+        *      one.
+        *
+        *      @fixme: pass the root into this function!
         */
-       if (takes_precedence) {
-               if (!fr_hash_table_replace(dict->values_by_da, enumv)) {
-                       fr_strerror_printf("%s: Failed inserting value %s", __FUNCTION__, alias);
-                       return -1;
-               }
-       } else {
-               (void) fr_hash_table_insert(dict->values_by_da, enumv);
+       da = fr_dict_attr_by_name(NULL, n->name);
+       if (da) {
+               fr_dict_unknown_free(&parent);
+               parent = n;
+               fr_dict_unknown_free(&parent);
+               return da;
        }
 
        /*
-        *      Mark the attribute up as having an enumv
+        *      Ensure the parent is freed at the same time as the
+        *      unknown DA.  This should be OK as we never parent
+        *      multiple unknown attributes off the same parent.
         */
-       {
-               fr_dict_attr_t *mutable;
+       if (new_parent && new_parent->flags.is_unknown) talloc_steal(n, new_parent);
 
-               memcpy(&mutable, &da, sizeof(mutable));
+       return n;
+}
 
-               mutable->flags.has_value = 1;
+/** Initialise a fr_dict_attr_t from an ASCII attribute and value
+ *
+ * Where the attribute name is in the form:
+ *  - Attr-%d
+ *  - Attr-%d.%d.%d...
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @param[in] ctx              to allocate the attribute in.
+ * @param[out] out             Where to write the new attribute to.
+ * @param[in] parent           of the unknown attribute (may also be unknown).
+ * @param[in] num              of the unknown attribute.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int dict_unknown_attr_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+                                      fr_dict_attr_t const *parent, unsigned long num)
+{
+       fr_dict_attr_t          *da;
+       fr_dict_attr_flags_t    flags = {
+                                       .is_unknown = true,
+                                       .is_raw = true,
+                               };
+
+       if (!fr_cond_assert(parent)) {
+               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+               return -1;
        }
 
+       *out = NULL;
+
+       da = dict_attr_alloc(ctx, parent, NULL, num, FR_TYPE_OCTETS, &flags);
+       if (!da) return -1;
+
+       *out = da;
+
        return 0;
 }
 
-/*
- *     String split routine.  Splits an input string IN PLACE
- *     into pieces, based on spaces.
+/** Create a fr_dict_attr_t from an ASCII attribute and value
+ *
+ * Where the attribute name is in the form:
+ *  - Attr-%d
+ *  - Attr-%d.%d.%d...
+ *
+ * @copybrief fr_dict_unknown_from_fields
+ *
+ * @note If vendor != 0, an unknown vendor (may) also be created, parented by
+ *     the correct EVS or VSA attribute. This is accessible via vp->parent,
+ *     and will be use the unknown da as its talloc parent.
+ *
+ * @param[in] ctx              to alloc new attribute in.
+ * @param[out] out             Where to write the head of the chain unknown
+ *                             dictionary attributes.
+ * @param[in] parent           Attribute to use as the root for resolving OIDs in.
+ *                             Usually the root of a protocol dictionary.
+ * @param[in] oid_str          of attribute.
+ * @return
+ *     - The number of bytes parsed on success.
+ *     - <= 0 on failure.  Negative offset indicates parse error position.
  */
-int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
+ssize_t fr_dict_unknown_afrom_oid_str(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+                                     fr_dict_attr_t const *parent, char const *oid_str)
 {
-       int argc = 0;
+       char const              *p = oid_str, *end = oid_str + strlen(oid_str);
+       fr_dict_attr_t const    *our_parent = parent;
+       TALLOC_CTX              *top_ctx = NULL, *our_ctx = ctx;
 
-       while (*str) {
-               if (argc >= max_argc) break;
+       fr_dict_attr_t          *n = NULL;
 
-               /*
-                *      Chop out comments early.
-                */
-               if (*str == '#') {
-                       *str = '\0';
-                       break;
-               }
+       if (!fr_cond_assert(parent)) {
+               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+               return -1;
+       }
 
-               while ((*str == ' ') ||
-                      (*str == '\t') ||
-                      (*str == '\r') ||
-                      (*str == '\n'))
-                       *(str++) = '\0';
+       *out = NULL;
 
-               if (!*str) break;
+       if (fr_dict_valid_name(oid_str, -1) < 0) return -1;
 
-               argv[argc] = str;
-               argc++;
-
-               while (*str &&
-                      (*str != ' ') &&
-                      (*str != '\t') &&
-                      (*str != '\r') &&
-                      (*str != '\n'))
-                       str++;
+       /*
+        *      All unknown attributes are of the form "Attr-#-#-#-#"
+        */
+       if (strncasecmp(p, "Attr-", 5) != 0) {
+               fr_strerror_printf("Unknown attribute '%s'", oid_str);
+               return 0;
        }
+       p += 5;
 
-       return argc;
-}
-
-static int dict_read_sscanf_i(unsigned int *pvalue, char const *str)
-{
-       int rcode = 0;
-       int base = 10;
-       static char const *tab = "0123456789";
+       do {
+               unsigned int            num;
+               fr_dict_attr_t const    *da = NULL;
 
-       if ((str[0] == '0') &&
-           ((str[1] == 'x') || (str[1] == 'X'))) {
-               tab = "0123456789abcdef";
-               base = 16;
+               if (fr_dict_oid_component(&num, &p) < 0) {
+               error:
+                       talloc_free(top_ctx);
+                       return -(p - oid_str);
+               }
 
-               str += 2;
-       }
+               switch (*p) {
+               /*
+                *      Structural attribute
+                */
+               case '.':
+                       if (!our_parent) goto is_root;
 
-       while (*str) {
-               char const *c;
+                       da = fr_dict_attr_child_by_num(our_parent, num);
+                       if (!da) {      /* Unknown component */
+                               switch (our_parent->type) {
+                               case FR_TYPE_EVS:
+                               case FR_TYPE_VSA:
+                                       da = fr_dict_attr_child_by_num(our_parent, num);
+                                       if (!fr_cond_assert(!da || (da->type == FR_TYPE_VENDOR))) goto error;
 
-               if (*str == '.') break;
+                                       if (!da) {
+                                               if (fr_dict_unknown_vendor_afrom_num(our_ctx, &n,
+                                                                                    our_parent, num) < 0) {
+                                                       goto error;
+                                               }
+                                               da = n;
+                                       }
+                                       break;
 
-               c = memchr(tab, tolower((int)*str), base);
-               if (!c) return 0;
+                               case FR_TYPE_TLV:
+                               case FR_TYPE_EXTENDED:
+                               case FR_TYPE_LONG_EXTENDED:
+                               is_root:
+                                       if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) {
+                                               goto error;
+                                       }
 
-               rcode *= base;
-               rcode += (c - tab);
-               str++;
-       }
+                                       da = n;
+                                       break;
 
-       *pvalue = rcode;
-       return 1;
-}
+                               /*
+                                *      Can't have a FR_TYPE_STRING inside a
+                                *      FR_TYPE_STRING (for example)
+                                */
+                               default:
+                                       fr_strerror_printf("Parent OID component (%s) in \"%.*s\" specified a "
+                                                          "non-structural type (%s)", our_parent->name,
+                                                          (int)(p - oid_str), oid_str,
+                                                          fr_int2str(dict_attr_types, our_parent->type, "<INVALID>"));
+                                       goto error;
+                               }
+                       }
+                       our_parent = da;
 
-/** Parser context for dict_from_file
- *
- * Allows vendor and TLV context to persist across $INCLUDEs
- */
-typedef struct {
-       fr_dict_t               *dict;                  //!< Protocol dictionary we're inserting attributes into.
-       fr_dict_t               *old_dict;              //!< The dictionary before the current BEGIN-PROTOCOL block.
+                       if (n && n->flags.is_unknown) {
+                               if (top_ctx == NULL) top_ctx = n;       /* Track first unknown */
+                               our_ctx = n;
+                       }
 
-       unsigned int            block_vendor;           //!< Vendor block we're inserting attributes into.
-                                                       //!< Can be removed once we remove the vendor field from
-                                                       //!< #fr_dict_attr_t.
 
-       fr_dict_attr_t const    *block_tlv[FR_DICT_TLV_NEST_MAX];       //!< Nested TLV block's we're
-                                                                       //!< inserting attributes into.
-       int                     block_tlv_depth;        //!< Nested TLV block index we're inserting into.
+                       break;
 
-       fr_dict_attr_t const    *parent;                //!< Current parent attribute (root/vendor/tlv).
-} dict_from_file_ctx_t;
+               /*
+                *      Leaf attribute
+                */
+               case '\0':
+                       if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) goto error;
+                       break;
+               }
+               p++;
+       } while (p < end);
 
-/** Lookup a dictionary reference
- *
- * Format is [<proto>].[<attr>]
- *
- * If protocol is omitted lookup is in the current dictionary.
- *
- * FIXME: Probably needs the dictionary equivalent of pass2, to fixup circular dependencies
- *     DHCPv4->RADIUS and RADIUS->DHCPv4 are both valid.
- *
- * @param[in] dict     The current dictionary we're parsing.
- * @param[in,out] ref  The reference string.  Pointer advanced to the end of the string.
- * @return
- *     - NULL if the reference is invalid.
- *     - A local or foreign attribute representing the target of the reference.
- */
-static fr_dict_attr_t const *dict_resolve_reference(fr_dict_t *dict, char const *ref)
-{
-       char const              *p = ref, *q, *end = p + strlen(ref);
-       fr_dict_t               *proto_dict;
-       fr_dict_attr_t const    *da;
+       if (!n) return 0;
 
        /*
-        *      If the reference does not begin with .
-        *      then it's a reference into a foreign
-        *      protocol.
+        *      Invert the talloc hierarchy, so that if the unknown
+        *      attribute is freed, any unknown parents are also freed.
         */
-       if (*p != '.') {
-               char buffer[FR_DICT_PROTO_MAX_NAME_LEN + 1];
+       for (our_parent = n->parent, our_ctx = n;
+            our_parent && our_parent->flags.is_unknown;
+            our_parent = our_parent->parent) {
+               fr_dict_attr_t *tmp;
 
-               q = strchr(p, '.');
-               if (!q) q = end;
+               memcpy(&tmp, &our_parent, sizeof(tmp));                 /* const issues *sigh* */
 
-               if ((size_t)(q - p) > sizeof(buffer)) {
-                       fr_strerror_printf("Protocol name too long");
-                       return NULL;
-               }
+               our_ctx = talloc_steal(our_ctx, tmp);
+       }
 
-               strlcpy(buffer, p, (q - p + 1));
-               p = q;
+       DA_VERIFY(n);
 
-               dict = fr_dict_by_protocol_name(buffer);
-               if (!dict) {
-                       fr_strerror_printf("Referenced protocol \"%s\" not found", buffer);
-                       return NULL;
-               }
+       *out = n;
 
-               return NULL;
-       /*
-        *      If the reference string begins with .
-        *      then the reference is in the current
-        *      dictionary.
-        */
-       } else {
-               proto_dict = dict;
-       }
+       return end - oid_str;
+}
+
+/** Create a dictionary attribute by name embedded in another string
+ *
+ * Find the first invalid attribute name char in the string pointed to by name.
+ *
+ * Copy the characters between the start of the name string and the first none
+ * #fr_dict_attr_allowed_chars char to a buffer and initialise da as an unknown
+ * attribute.
+ *
+ * @param[in] ctx              To allocate unknown #fr_dict_attr_t in.
+ * @param[out] out             Where to write the head of the chain unknown
+ *                             dictionary attributes.
+ * @param[in] parent           Attribute to use as the root for resolving OIDs in.
+ *                             Usually the root of a protocol dictionary.
+ * @param[in] name             string start.
+ * @return
+ *     - <= 0 on failure.
+ *     - The number of bytes of name consumed on success.
+ */
+ssize_t fr_dict_unknown_afrom_oid_substr(TALLOC_CTX *ctx, fr_dict_attr_t **out,
+                                        fr_dict_attr_t const *parent, char const *name)
+{
+       char const      *p;
+       size_t          len;
+       char            buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
+       ssize_t         slen;
+
+       if (!name || !*name) return 0;
 
        /*
-        *      If there's a '.' after the dictionary, then
-        *      the reference is to a specific attribute.
+        *      Advance p until we get something that's not part of
+        *      the dictionary attribute name.
         */
-       if (*p == '.') {
-               p++;
+       for (p = name; fr_dict_attr_allowed_chars[(int)*p] || (*p == '.') || (*p == '-'); p++);
 
-               da = fr_dict_attr_by_name_substr(proto_dict, &p);
-               if (!da) {
-                       fr_strerror_printf("Referenced attribute \"%s\" not found", p);
-                       return NULL;
-               }
+       len = p - name;
+       if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
+               fr_strerror_printf("Attribute name too long");
+               return 0;
        }
-
-       da = fr_dict_root(proto_dict);
-       if (!da) {
-               fr_strerror_printf("Dictionary missing attribute root");
-               return NULL;
+       if (len == 0) {
+               fr_strerror_printf("Invalid attribute name");
+               return 0;
        }
+       strlcpy(buffer, name, len + 1);
 
-       return da;
+       slen = fr_dict_unknown_afrom_oid_str(ctx, out, parent, buffer);
+       if (slen <= 0) return slen;
+
+       return p - name;
 }
 
-/*
- *     Process the ATTRIBUTE command
+
+/** Check to see if we can convert a nested TLV structure to known attributes
+ *
+ * @param[in] dict                     to search in.
+ * @param[in] da                       Nested tlv structure to convert.
+ * @return
+ *     - NULL if we can't.
+ *     - Known attribute if we can.
  */
-static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
-                                      unsigned int block_vendor, char **argv, int argc,
-                                      fr_dict_attr_flags_t *base_flags)
+fr_dict_attr_t const *fr_dict_attr_known(fr_dict_t *dict, fr_dict_attr_t const *da)
 {
-       bool                    oid = false;
+       INTERNAL_IF_NULL(dict);
 
-       unsigned int            vendor = 0;
-       unsigned int            attr;
+       if (!da->flags.is_unknown) return da;   /* It's known */
 
-       int                     type;
-       unsigned int            length;
-       fr_dict_attr_flags_t    flags;
-       fr_dict_attr_t const    *ref = NULL;
-       char                    *p;
+       if (da->parent) {
+               fr_dict_attr_t const *parent;
 
-       if ((argc < 3) || (argc > 4)) {
-               fr_strerror_printf("Invalid ATTRIBUTE syntax");
-               return -1;
-       }
+               parent = fr_dict_attr_known(dict, da->parent);
+               if (!parent) return NULL;
 
-       /*
-        *      Dictionaries need to have real names, not shitty ones.
-        */
-       if (strncmp(argv[0], "Attr-", 5) == 0) {
-               fr_strerror_printf("Invalid ATTRIBUTE name");
-               return -1;
+               return fr_dict_attr_child_by_num(parent, da->attr);
        }
 
-       memcpy(&flags, base_flags, sizeof(flags));
+       if (dict->root == da) return dict->root;
+       return NULL;
+}
 
-       /*
-        *      Look for OIDs before doing anything else.
-        */
-       if (!strchr(argv[1], '.')) {
-               /*
-                *      Parse out the attribute number
-                */
-               if (!dict_read_sscanf_i(&attr, argv[1])) {
-                       fr_strerror_printf("Invalid ATTRIBUTE number");
-                       return -1;
-               }
+static void dict_snprint_flags(char *out, size_t outlen, fr_dict_attr_flags_t flags)
+{
+       char *p = out, *end = p + outlen;
+       size_t len;
 
-               /*
-                *      Got an OID string.  Every attribute should exist other
-                *      than the leaf, which is the attribute we're defining.
-                */
-       } else {
-               ssize_t slen;
+       out[0] = '\0';
 
-               oid = true;
+#define FLAG_SET(_flag) \
+do { \
+       if (flags._flag) {\
+               p += strlcpy(p, STRINGIFY(_flag)",", end - p);\
+               if (p >= end) return;\
+       }\
+} while (0)
 
-               slen = fr_dict_attr_by_oid(dict, &parent, &attr, argv[1]);
-               if (slen <= 0) return -1;
+       FLAG_SET(is_root);
+       FLAG_SET(is_unknown);
+       FLAG_SET(is_raw);
+       FLAG_SET(internal);
+       FLAG_SET(has_tag);
+       FLAG_SET(array);
+       FLAG_SET(has_value);
+       FLAG_SET(concat);
+       FLAG_SET(virtual);
+       FLAG_SET(compare);
 
-               if (!fr_cond_assert(parent)) return -1; /* Should have provided us with a parent */
+       if (flags.encrypt) {
+               p += snprintf(p, end - p, "encrypt=%i,", flags.encrypt);
+               if (p >= end) return;
        }
 
-       /*
-        *      Some types can have fixed length
-        */
-       p = strchr(argv[2], '[');
-       if (p) *p = '\0';
+       if (flags.length) {
+               p += snprintf(p, end - p, "length=%i,", flags.length);
+               if (p >= end) return;
+       }
+
+       if (!out[0]) return;
 
        /*
-        *      find the type of the attribute.
+        *      Trim the comma
         */
-       type = fr_str2int(dict_attr_types, argv[2], -1);
-       if (type < 0) {
-               fr_strerror_printf("Unknown data type '%s'", argv[2]);
-               return -1;
-       }
+       len = strlen(out);
+       if (out[len - 1] == ',') out[len - 1] = '\0';
+}
 
-       if (p) {
-               char *q;
+void fr_dict_print(fr_dict_attr_t const *da, int depth)
+{
+       char buff[256];
+       unsigned int i;
+       char const *name;
 
-               q = strchr(p + 1, ']');
-               if (!q) {
-                       fr_strerror_printf("Invalid format for '%s[...]'", argv[2]);
-                       return -1;
-               }
+       dict_snprint_flags(buff, sizeof(buff), da->flags);
 
-               *q = '\0';
+       switch (da->type) {
+       case FR_TYPE_VSA:
+               name = "VSA";
+               break;
 
-               if (!dict_read_sscanf_i(&length, p + 1)) {
-                       fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
-                       return -1;
-               }
+       case FR_TYPE_EXTENDED:
+               name = "EXTENDED";
+               break;
 
-               if ((length == 0) || (length > 253)) {
-                       fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
-                       return -1;
-               }
+       case FR_TYPE_TLV:
+               name = "TLV";
+               break;
 
-               flags.length = length;
-       }
+       case FR_TYPE_EVS:
+               name = "EVS";
+               break;
 
-       /*
-        *      Parse options.
-        */
-       if (argc >= 4) {
-               char *q, *v;
+       case FR_TYPE_VENDOR:
+               name = "VENDOR";
+               break;
 
-               p = argv[3];
-               do {
-                       char key[64], value[256];
+       case FR_TYPE_LONG_EXTENDED:
+               name = "LONG EXTENDED";
+               break;
 
-                       q = strchr(p, ',');
-                       if (!q) q = p + strlen(p);
+       case FR_TYPE_STRUCT:
+               name = "STRUCT";
+               break;
 
-                       /*
-                        *      Nothing after the trailing comma
-                        */
-                       if (p == q) break;
+       default:
+               name = "ATTRIBUTE";
+               break;
+       }
 
-                       if ((size_t)(q - p) > sizeof(key)) {
-                               fr_strerror_printf("ATTRIBUTE option key too long");
-                               return -1;
-                       }
+       printf("%u%.*s%s \"%s\" vendor: %x (%u), num: %x (%u), type: %s, flags: %s\n", da->depth, depth,
+              "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t", name, da->name,
+              fr_dict_vendor_num_by_da(da), fr_dict_vendor_num_by_da(da), da->attr, da->attr,
+              fr_int2str(dict_attr_types, da->type, "?Unknown?"), buff);
 
-                       /*
-                        *      Copy key and value
-                        */
-                       if (!(v = memchr(p, '=', q - p)) || (v == q)) {
-                               value[0] = '\0';
-                               strlcpy(key, p, (q - p) + 1);
-                       } else {
-                               strlcpy(key, p, (v - p) + 1);
-                               strlcpy(value, v + 1, q - v);
-                       }
+       if (da->children) for (i = 0; i < talloc_array_length(da->children); i++) {
+               if (da->children[i]) {
+                       fr_dict_attr_t const *bin;
 
-                       /*
-                        *      Boolean flag, means this is a tagged
-                        *      attribute.
-                        */
-                       if (strcmp(key, "has_tag") == 0) {
-                               flags.has_tag = 1;
+                       for (bin = da->children[i]; bin; bin = bin->next) fr_dict_print(bin, depth + 1);
+               }
+       }
+}
 
-                       /*
-                        *      Encryption method.
-                        */
-                       } else if (strcmp(key, "encrypt") == 0) {
-                               char *qq;
+/** Find a common ancestor that two TLV type attributes share
+ *
+ * @param[in] a                        first TLV attribute.
+ * @param[in] b                        second TLV attribute.
+ * @param[in] is_ancestor      Enforce a->b relationship (a is parent or ancestor of b).
+ * @return
+ *     - Common ancestor if one exists.
+ *     - NULL if no common ancestor exists.
+ */
+fr_dict_attr_t const *fr_dict_parent_common(fr_dict_attr_t const *a, fr_dict_attr_t const *b, bool is_ancestor)
+{
+       unsigned int i;
+       fr_dict_attr_t const *p_a, *p_b;
 
-                               flags.encrypt = strtol(value, &qq, 0);
-                               if (*qq) {
-                                       fr_strerror_printf("Invalid encrypt value \"%s\"", value);
-                                       return -1;
-                               }
+       if (!a || !b) return NULL;
 
-                       /*
-                        *      Marks the attribute up as internal.
-                        *      This means it can use numbers outside of the allowed
-                        *      protocol range, and also means it will not be included
-                        *      in replies or proxy requests.
-                        */
-                       } else if (strcmp(key, "internal") == 0) {
-                               flags.internal = 1;
+       if (is_ancestor && (b->depth <= a->depth)) return NULL;
 
-                       } else if (strcmp(key, "array") == 0) {
-                               flags.array = 1;
+       /*
+        *      Find a common depth to work back from
+        */
+       if (a->depth > b->depth) {
+               p_b = b;
+               for (p_a = a, i = a->depth - b->depth; p_a && (i > 0); p_a = p_a->parent, i--);
+       } else if (a->depth < b->depth) {
+               p_a = a;
+               for (p_b = b, i = b->depth - a->depth; p_b && (i > 0); p_b = p_b->parent, i--);
+       } else {
+               p_a = a;
+               p_b = b;
+       }
 
-                       } else if (strcmp(key, "concat") == 0) {
-                               flags.concat = 1;
+       while (p_a && p_b) {
+               if (p_a == p_b) return p_a;
 
-                       } else if (strcmp(key, "virtual") == 0) {
-                               flags.virtual = 1;
+               p_a = p_a->parent;
+               p_b = p_b->parent;
+       }
 
-                       } else if (strcmp(key, "reference") == 0) {
-                               ref = dict_resolve_reference(dict, value);
-                               if (!ref) return -1;
-                               flags.is_reference = 1;
+       return NULL;
+}
 
-                       /*
-                        *      The only thing is the vendor name, and it's a known name:
-                        *      allow it.
-                        */
-                       } else if ((argv[3] == p) && (*q == '\0')) {
-                               if (oid) {
-                                       fr_strerror_printf("ATTRIBUTE cannot use a 'vendor' flag");
-                                       return -1;
-                               }
-
-                               if (block_vendor) {
-                                       fr_strerror_printf("Vendor flag inside of 'BEGIN-VENDOR' is not allowed");
-                                       return -1;
-                               }
+/** Process a single OID component
+ *
+ * @param[out] out             Value of component.
+ * @param[in] oid              string to parse.
+ * @return
+ *     - 0 on success.
+ *     - -1 on format error.
+ */
+int fr_dict_oid_component(unsigned int *out, char const **oid)
+{
+       char const *p = *oid;
+       char *q;
+       unsigned long num;
 
-                               vendor = fr_dict_vendor_by_name(dict, key);
-                               if (!vendor) goto unknown;
-                               break;
+       *out = 0;
 
-                       } else {
-                       unknown:
-                               fr_strerror_printf("Unknown option '%s'", key);
-                               return -1;
-                       }
-                       p = q;
-               } while (*p++);
+       num = strtoul(p, &q, 10);
+       if ((p == q) || (num == ULONG_MAX)) {
+               fr_strerror_printf("Invalid OID component \"%s\" (%lu)", p, num);
+               return -1;
        }
 
-#ifdef WITH_DICTIONARY_WARNINGS
-       /*
-        *      Hack to help us discover which vendors have illegal
-        *      attributes.
-        */
-       if (!vendor && (attr < 256) &&
-           !strstr(fn, "rfc") && !strstr(fn, "illegal")) {
-               fprintf(stderr, "WARNING: Illegal Attribute %s in %s\n",
-                       argv[0], fn);
-       }
-#endif
+       switch (*q) {
+       case '\0':
+       case '.':
+               *oid = q;
+               *out = (unsigned int)num;
 
-       /*
-        *      Add in a normal attribute
-        */
-       if (!ref) {
-               if (fr_dict_attr_add(dict, parent, argv[0], attr, type, &flags) < 0) return -1;
-       /*
-        *      Add in a special reference attribute
-        */
-       } else {
-               if (dict_attr_ref_add(dict, parent, argv[0], attr, type, &flags, ref) < 0) return -1;
-       }
+               return 0;
 
-       return 0;
+       default:
+               fr_strerror_printf("Unexpected text after OID component");
+               *out = 0;
+               return -1;
+       }
 }
 
-/*
- *     Process the ATTRIBUTE command, where it only has a name.
+/** Build the tlv_stack for the specified DA and encode the path in OID form
+ *
+ * @param[out] out             Where to write the OID.
+ * @param[in] outlen           Length of the output buffer.
+ * @param[in] ancestor         If not NULL, only print OID portion between
+ *                             ancestor and da.
+ * @param[in] da               to print OID string for.
+ * @return the number of bytes written to the buffer.
  */
-static int dict_read_process_named_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
-                                            char **argv, int argc,
-                                            fr_dict_attr_flags_t const *base_flags)
+size_t fr_dict_print_attr_oid(char *out, size_t outlen,
+                             fr_dict_attr_t const *ancestor, fr_dict_attr_t const *da)
 {
-       int type;
-       unsigned int attr;
-       uint32_t hash;
-       char *p, normalized[512];
+       size_t                  len;
+       char                    *p = out, *end = p + outlen;
+       int                     i;
+       int                     depth = 0;
+       fr_dict_attr_t const    *tlv_stack[FR_DICT_MAX_TLV_STACK + 1];
 
-       if (argc != 2) {
-               fr_strerror_printf("Invalid ATTRIBUTE syntax");
-               return -1;
-       }
+       if (!outlen) return 0;
 
        /*
-        *      find the type of the attribute.
+        *      If the ancestor and the DA match, there's
+        *      no OID string to print.
         */
-       type = fr_str2int(dict_attr_types, argv[1], -1);
-       if (type < 0) {
-               fr_strerror_printf("Unknown data type '%s'", argv[1]);
-               return -1;
+       if (ancestor == da) {
+               out[0] = '\0';
+               return 0;
        }
 
-       strlcpy(normalized, argv[0], sizeof(normalized));
-       for (p = normalized; *p != '\0'; p++) {
-               if (isupper((int) *p)) {
-                       *p = tolower((int) *p);
+       fr_proto_tlv_stack_build(tlv_stack, da);
+
+       if (ancestor) {
+               if (tlv_stack[ancestor->depth - 1] != ancestor) {
+                       fr_strerror_printf("Attribute \"%s\" is not a descendent of \"%s\"", da->name, ancestor->name);
+                       return -1;
                }
+               depth = ancestor->depth;
        }
 
-       hash = fr_hash_string(normalized);
-       attr = hash;
-
        /*
-        *      Add it in.
+        *      We don't print the ancestor, we print the OID
+        *      between it and the da.
         */
-       if (fr_dict_attr_add(dict, parent, argv[0], attr, type, base_flags) < 0) return -1;
+       len = snprintf(p, end - p, "%u", tlv_stack[depth]->attr);
+       if ((p + len) >= end) return p - out;
+       p += len;
 
-       return 0;
+
+       for (i = depth + 1; i < (int)da->depth; i++) {
+               len = snprintf(p, end - p, ".%u", tlv_stack[i]->attr);
+               if ((p + len) >= end) return p - out;
+               p += len;
+       }
+
+       return p - out;
 }
 
-/** Process a value alias
+/** Get the leaf attribute of an OID string
+ *
+ * @note On error, vendor will be set (if present), parent will be the
+ *     maximum depth we managed to resolve to, and attr will be the child
+ *     we failed to resolve.
  *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[out] attr            Number we parsed.
+ * @param[in,out] parent       attribute (or root of dictionary).
+ *                             Will be updated to the parent directly beneath the leaf.
+ * @param[in] oid              string to parse.
+ * @return
+ *     - > 0 on success (number of bytes parsed).
+ *     - <= 0 on parse error (negative offset of parse error).
  */
-static int dict_read_process_value(fr_dict_t *dict, char **argv, int argc)
+ssize_t fr_dict_attr_by_oid(fr_dict_t *dict, fr_dict_attr_t const **parent, unsigned int *attr, char const *oid)
 {
-       static fr_dict_attr_t const     *last_attr = NULL;
-       fr_dict_attr_t const            *da;
-       fr_value_box_t                  value;
+       char const              *p = oid;
+       unsigned int            num = 0;
+       ssize_t                 slen;
 
-       if (argc != 3) {
-               fr_strerror_printf("Invalid VALUE syntax");
-               return -1;
+       if (!fr_cond_assert(parent)) return 0;
+       INTERNAL_IF_NULL(dict);
+
+       *attr = 0;
+
+       if (fr_dict_oid_component(&num, &p) < 0) return oid - p;
+
+       /*
+        *      Record progress even if we error out.
+        *
+        *      Don't change this, you will break things.
+        */
+       *attr = num;
+
+       switch ((*parent)->type) {
+       case FR_TYPE_STRUCTURAL:
+               break;
+
+       default:
+               fr_strerror_printf("Attribute %s (%i) is not a TLV, so cannot contain a child attribute.  "
+                                  "Error at sub OID \"%s\"", (*parent)->name, (*parent)->attr, oid);
+               return 0;       /* We parsed nothing */
        }
 
        /*
-        *      Most VALUEs are bunched together by ATTRIBUTE.  We can
-        *      save a lot of lookups on dictionary initialization by
-        *      caching the last attribute.
+        *      If it's not a vendor type, it must be between 0..8*type_size
+        *
+        *      @fixme: find the TLV parent, and check it's size
         */
-       if (last_attr && (strcasecmp(argv[0], last_attr->name) == 0)) {
-               da = last_attr;
-       } else {
-               da = fr_dict_attr_by_name(dict, argv[0]);
-               last_attr = da;
+       if (((*parent)->type != FR_TYPE_VENDOR) && ((*parent)->type != FR_TYPE_VSA) && !(*parent)->flags.is_root &&
+           (num > UINT8_MAX)) {
+               fr_strerror_printf("TLV attributes must be between 0..255 inclusive");
+               return 0;
        }
 
+       switch (p[0]) {
        /*
-        *      Remember which attribute is associated with this
-        *      value.  This allows us to define enum
-        *      values before the attribute exists, and fix them
-        *      up later.
+        *      We've not hit the leaf yet, so the attribute must be
+        *      defined already.
         */
-       if (!da) {
-               dict_enum_fixup_t *fixup;
+       case '.':
+       {
+               fr_dict_attr_t const *child;
+               p++;
 
-               fixup = talloc_zero(dict->pool, dict_enum_fixup_t);
-               if (!fixup) {
-               oom:
-                       talloc_free(fixup);
-                       fr_strerror_printf("Out of memory");
-                       return -1;
+               child = fr_dict_attr_child_by_num(*parent, num);
+               if (!child) {
+                       fr_strerror_printf("Unknown attribute \"%i\" in OID string \"%s\"", num, oid);
+                       return 0;       /* We parsed nothing */
                }
-               fixup->attribute = talloc_strdup(fixup, argv[0]);
-               if (!fixup->attribute) goto oom;
-               fixup->alias = talloc_strdup(fixup, argv[1]);
-               if (!fixup->alias) goto oom;
-               fixup->value = talloc_strdup(fixup, argv[2]);
-               if (!fixup->value) goto oom;
 
                /*
-                *      Insert to the head of the list.
+                *      Record progress even if we error out.
+                *
+                *      Don't change this, you will break things.
                 */
-               fixup->next = dict->enum_fixup;
-               dict->enum_fixup = fixup;
+               *parent = child;
 
-               return 0;
+               slen = fr_dict_attr_by_oid(dict, parent, attr, p);
+               if (slen <= 0) return slen - (p - oid);
+               return slen + (p - oid);
        }
 
-       {
-               fr_type_t type = da->type;      /* Might change - Stupid combo IP */
+       /*
+        *      Hit the leaf, this is the attribute we need to define.
+        */
+       case '\0':
+               *attr = num;
+               return p - oid;
 
-               if (fr_value_box_from_str(NULL, &value, &type, NULL, argv[2], -1, '\0', false) < 0) {
-                       fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
-                       return -1;
-               }
-       }
-
-       if (fr_dict_enum_add_alias(da, argv[1], &value, false, true) < 0) {
-               fr_value_box_clear(&value);
-               return -1;
+       default:
+               fr_strerror_printf("Malformed OID string, got trailing garbage '%s'", p);
+               return oid - p;
        }
-       fr_value_box_clear(&value);
-
-       return 0;
 }
 
-/*
- *     Process the FLAGS command
+/** Return the root attribute of a dictionary
+ *
+ * @param dict                 to return root for.
+ * @return the root attribute of the dictionary.
  */
-static int dict_read_process_flags(UNUSED fr_dict_t *dict, char **argv, int argc,
-                                  fr_dict_attr_flags_t *base_flags)
+fr_dict_attr_t const *fr_dict_root(fr_dict_t const *dict)
 {
-       bool sense = true;
-
-       if (argc == 1) {
-               char *p;
+       return dict->root;
+}
 
-               p = argv[0];
-               if (*p == '!') {
-                       sense = false;
-                       p++;
-               }
+/** Lookup a protocol by its name
+ *
+ * @param[in] name of the protocol to locate.
+ * @return
+ *     - Attribute matching name.
+ *     - NULL if no matching protocolibute could be found.
+ */
+fr_dict_t *fr_dict_by_protocol_name(char const *name)
+{
+       fr_dict_attr_t  root = { .name = name };
+       fr_dict_t       find = { .root = &root };
 
-               if (strcmp(p, "internal") == 0) {
-                       base_flags->internal = sense;
-                       return 0;
-               }
-       }
+       if (!protocol_by_name || !name) return NULL;
 
-       fr_strerror_printf("Invalid FLAGS syntax");
-       return -1;
+       return fr_hash_table_finddata(protocol_by_name, &find);
 }
 
-static int dict_read_parse_format(char const *format, unsigned int *pvalue, int *ptype, int *plength,
-                                 bool *pcontinuation)
+/** Lookup a protocol by its number.
+ *
+ * Returns the #fr_dict_t belonging to the protocol with the specified number
+ * if any have been registered.
+ *
+ * @param[in] num to search for.
+ * @return dictionary representing the protocol (if it exists).
+ */
+fr_dict_t *fr_dict_by_protocol_num(unsigned int num)
 {
-       char const *p;
-       int type, length;
-       bool continuation = false;
+       fr_dict_t       find;
+       fr_dict_attr_t  root;
 
-       if (strncasecmp(format, "format=", 7) != 0) {
-               fr_strerror_printf("Invalid format for VENDOR.  Expected 'format=', got '%s'",
-                                  format);
-               return -1;
-       }
+       if (!protocol_by_num) return NULL;
 
-       p = format + 7;
-       if ((strlen(p) < 3) ||
-           !isdigit((int)p[0]) ||
-           (p[1] != ',') ||
-           !isdigit((int)p[2]) ||
-           (p[3] && (p[3] != ','))) {
-               fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
-                                  p);
-               return -1;
-       }
+       memset(&find, 0, sizeof(find));
+       memset(&root, 0, sizeof(root));
 
-       type = (int)(p[0] - '0');
-       length = (int)(p[2] - '0');
+       find.root = &root;
+       root.attr = num;
 
-       if ((type != 1) && (type != 2) && (type != 4)) {
-               fr_strerror_printf("Invalid type value %d for VENDOR", type);
-               return -1;
-       }
+       return fr_hash_table_finddata(protocol_by_num, &find);
+}
 
-       if ((length != 0) && (length != 1) && (length != 2)) {
-               fr_strerror_printf("Ivalid length value %d for VENDOR", length);
-               return -1;
-       }
+/** Dictionary/attribute ctx struct
+ *
+ */
+typedef struct {
+       fr_dict_t               *found_dict;    //!< Dictionary attribute found in.
+       fr_dict_attr_t const    *found_da;      //!< Resolved attribute.
+       fr_dict_attr_t const    *find;          //!< Attribute to find.
+} dict_attr_search_t;
 
-       if (p[3] == ',') {
-               if (!p[4]) {
-                       fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
-                                          p);
-                       return -1;
-               }
+/** Search for an attribute name in all dictionaries
+ *
+ * @param[in] ctx      Attribute to search for.
+ * @param[in] data     Dictionary to search in.
+ * @return
+ *     - 0 if attribute not found in dictionary.
+ *     - 1 if attribute found in dictionary.
+ */
+static int _dict_attr_find_in_dicts(void *ctx, void *data)
+{
+       dict_attr_search_t      *search = ctx;
+       fr_dict_t               *dict;
 
-               if ((p[4] != 'c') ||
-                   (p[5] != '\0')) {
-                       fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
-                                          p);
-                       return -1;
-               }
-               continuation = true;
+       if (!data) return 0;    /* We get called with NULL data */
 
-               if ((*pvalue != VENDORPEC_WIMAX) ||
-                   (type != 1) || (length != 1)) {
-                       fr_strerror_printf("Only WiMAX VSAs can have continuations");
-                       return -1;
-               }
-       }
+       dict = talloc_get_type_abort(data, fr_dict_t);
 
-       *ptype = type;
-       *plength = length;
-       *pcontinuation = continuation;
-       return 0;
+       search->found_da = fr_hash_table_finddata(dict->attributes_by_name, search->find);
+       if (!search->found_da) return 0;
+
+       search->found_dict = data;
+
+       return 1;
 }
 
-/** Register the specified dictionary as a protocol dictionary
+/** Attempt to locate the protocol dictionary containing an attribute
  *
- * Allows vendor and TLV context to persist across $INCLUDEs
+ * @note Unlike fr_dict_by_attr_name, doesn't search through all the dictionaries,
+ *     just uses the fr_dict_attr_t hierarchy and the talloc hierarchy to locate
+ *     the dictionary (much much faster and more scalable).
+ *
+ * @param[in] da               To get the containing dictionary for.
+ * @return
+ *     - The dictionary containing da.
+ *     - NULL.
  */
-static int dict_read_process_protocol(char **argv, int argc)
+fr_dict_t *fr_dict_by_da(fr_dict_attr_t const *da)
 {
-       unsigned int    value;
-       unsigned int    type_size = 1;
-       fr_dict_t       *dict;
+       fr_dict_attr_t const *da_p = da;
 
-       if ((argc < 2) || (argc > 3)) {
-               fr_strerror_printf("Missing arguments after PROTOCOL.  Expected PROTOCOL <num> <name>");
-               return -1;
+       while (da_p->parent) {
+               da_p = da_p->parent;
+               DA_VERIFY(da_p);
        }
 
-       /*
-        *       Validate all entries
-        */
-       if (!dict_read_sscanf_i(&value, argv[1])) {
-               fr_strerror_printf("Invalid number '%s' following PROTOCOL", argv[1]);
-               return -1;
+       if (!da_p->flags.is_root) {
+               fr_strerror_printf("%s: Attribute %s has not been inserted into a dictionary", __FUNCTION__, da->name);
+               return NULL;
        }
 
        /*
-        *      Look for a format statement.  This may specify the
-        *      type length of the protocol's types.
+        *      Parent of the root attribute must
+        *      be the dictionary.
         */
-       if (argc == 3) {
-               char const *p;
-               char *q;
-
-               if (strncasecmp(argv[2], "format=", 7) != 0) {
-                       fr_strerror_printf("Invalid format for PROTOCOL.  Expected 'format=', got '%s'", argv[2]);
-                       return -1;
-               }
-               p = argv[2] + 7;
+       return talloc_get_type_abort(talloc_parent(da_p), fr_dict_t);
+}
 
-               type_size = strtoul(p, &q, 10);
-               if (q != (p + strlen(p))) {
-                       fr_strerror_printf("Found trailing garbage '%s' after format specifier", p);
-                       return -1;
-               }
-       }
+/** Attempt to locate the protocol dictionary containing an attribute
+ *
+ * @note This is O(n) and will only return the first instance of the dictionary.
+ *
+ * @param[out] found   the attribute that was resolved from the name.
+ * @param[in] name     the name of the attribute.
+ * @return
+ *     - the dictionary the attribute was found in.
+ *     - NULL if an attribute with the specified name wasn't found in any dictionary.
+ */
+fr_dict_t *fr_dict_by_attr_name(fr_dict_attr_t const **found, char const *name)
+{
+       fr_dict_attr_t          find = {
+                                       .name = name
+                               };
+       dict_attr_search_t      search = {
+                                       .find = &find
+                               };
+       int                     ret;
 
-       dict = fr_dict_by_protocol_num(value);
-       if (dict) {
-               if (dict->root->flags.type_size != type_size) {
-                       fr_strerror_printf("Conflicting flags for PROTOCOL \"%s\"", dict->root->name);
-                       return -1;
-               }
-               return 0;
-       }
+       *found = NULL;
 
-       dict = dict_alloc(NULL);
+       if (!name || !*name) return NULL;
 
-       /*
-        *      Set the root attribute with the protocol name
-        */
-       dict_root_set(dict, argv[0], value);
+       ret = fr_hash_table_walk(protocol_by_name, _dict_attr_find_in_dicts, &search);
+       if (ret == 0) return NULL;
 
-       if (dict_protocol_add(dict) < 0) return -1;
+       if (found) *found = search.found_da;
 
-       return 0;
+       return search.found_dict;
 }
 
-/*
- *     Process the VENDOR command
+/** Look up a vendor by its name
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] name             to search for.
+ * @return
+ *     - The vendor.
+ *     - NULL if no vendor with that name was regitered for this protocol.
  */
-static int dict_read_process_vendor(fr_dict_t *dict, char **argv, int argc)
+int fr_dict_vendor_by_name(fr_dict_t const *dict, char const *name)
 {
-       unsigned int                    value;
-       int                             type, length;
-       bool                            continuation = false;
-       fr_dict_vendor_t const          *dv;
-       fr_dict_vendor_t                *mutable;
-
-       if ((argc < 2) || (argc > 3)) {
-               fr_strerror_printf("Invalid VENDOR syntax");
-               return -1;
-       }
+       fr_dict_vendor_t find = { .name = name }, *found;
 
-       /*
-        *       Validate all entries
-        */
-       if (!dict_read_sscanf_i(&value, argv[1])) {
-               fr_strerror_printf("Invalid number in VENDOR");
-               return -1;
-       }
+       if (!name) return 0;
+       INTERNAL_IF_NULL(dict);
 
-       /* Create a new VENDOR entry for the list */
-       if (dict_vendor_add(dict, argv[0], value) < 0) return -1;
+       found = fr_hash_table_finddata(dict->vendors_by_name, &find);
+       if (!found) return 0;
 
-       /*
-        *      Look for a format statement.  Allow it to over-ride the hard-coded formats below.
-        */
-       if (argc == 3) {
-               if (dict_read_parse_format(argv[2], &value, &type, &length, &continuation) < 0) return -1;
+       return found->vendorpec;
+}
 
-       } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */
-               type = 4;
-               length = 0;
+/** Look up a vendor by its PEN
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] vendorpec                to search for.
+ * @return
+ *     - The vendor.
+ *     - NULL if no vendor with that number was regitered for this protocol.
+ */
+fr_dict_vendor_t const *fr_dict_vendor_by_num(fr_dict_t const *dict, int vendorpec)
+{
+       fr_dict_vendor_t dv;
 
-       } else if (value == VENDORPEC_LUCENT) {
-               type = 2;
-               length = 1;
+       INTERNAL_IF_NULL(dict);
 
-       } else if (value == VENDORPEC_STARENT) {
-               type = 2;
-               length = 2;
+       dv.vendorpec = vendorpec;
 
-       } else {
-               type = length = 1;
-       }
+       return fr_hash_table_finddata(dict->vendors_by_num, &dv);
+}
 
-       dv = fr_dict_vendor_by_num(dict, value);
-       if (!dv) {
-               fr_strerror_printf("Failed adding format for VENDOR");
-               return -1;
-       }
+/** Look up a vendor by one of its child attributes
+ *
+ * @param[in] da       The vendor attribute.
+ * @return
+ *     - The vendor.
+ *     - NULL if no vendor with that number was regitered for this protocol.
+ */
+fr_dict_vendor_t const *fr_dict_vendor_by_da(fr_dict_attr_t const *da)
+{
+       fr_dict_t               *dict;
+       fr_dict_vendor_t        dv;
 
-       memcpy(&mutable, &dv, sizeof(mutable));
+       dv.vendorpec = fr_dict_vendor_num_by_da(da);
+       if (!dv.vendorpec) return NULL;
 
-       mutable->type = type;
-       mutable->length = length;
-       mutable->flags = continuation;
+       dict = fr_dict_by_da(da);
 
-       return 0;
+       return fr_hash_table_finddata(dict->vendors_by_num, &dv);
 }
 
-/** Parse a dictionary file
+/** Return the vendor that parents this attribute
  *
- * @param[in] ctx      Contains the current state of the dictionary parser.
- *                     Used to track what PROTOCOL, VENDOR or TLV block
- *                     we're in. Block context changes in $INCLUDEs should
- *                     not affect the context of the including file.
- * @param[in] dir_name Directory containing the dictionary we're loading.
- * @param[in] filename we're parsing.
- * @param[in] src_file The including file.
- * @param[in] src_line Line on which the $INCLUDE or $INCLUDE- statement was found.
+ * @note Uses the dictionary hierachy to determine the parent
+ *
+ * @param[in] da               The dictionary attribute to find parent for.
  * @return
- *     - 0 on success.
- *     - -1 on failure.
+ *     - NULL if the attribute has no vendor.
+ *     - A fr_dict_attr_t representing this attribute's associated vendor.
  */
-static int _dict_from_file(dict_from_file_ctx_t *ctx,
-                          char const *dir_name, char const *filename,
-                          char const *src_file, int src_line)
+fr_dict_attr_t const *fr_dict_vendor_attr_by_da(fr_dict_attr_t const *da)
 {
-       FILE                    *fp;
-       char                    dir[256], fn[256];
-       char                    buf[256];
-       char                    *p;
-       int                     line = 0;
-
-       struct stat             statbuf;
-       char                    *argv[MAX_ARGV];
-       int                     argc;
-       fr_dict_attr_t const    *da;
-
-       /*
-        *      Base flags are only set for the current file
-        */
-       fr_dict_attr_flags_t    base_flags;
+       fr_dict_attr_t const *da_p = da;
 
-       if (!fr_cond_assert(!ctx->dict->root || ctx->parent)) return -1;
+       VERIFY_DA(da);
 
-       if ((strlen(dir_name) + 3 + strlen(filename)) > sizeof(dir)) {
-               fr_strerror_printf_push("%s: Filename name too long", "Error reading dictionary");
-               return -1;
+       while (da_p->parent) {
+               if (da_p->type == FR_TYPE_VENDOR) break;
+               da_p = da_p->parent;
        }
+       if (da_p->type != FR_TYPE_VENDOR) return NULL;
 
-       /*
-        *      If it's an absolute dir, forget the parent dir,
-        *      and remember the new one.
-        *
-        *      If it's a relative dir, tack on the current filename
-        *      to the parent dir.  And use that.
-        */
-       if (!FR_DIR_IS_RELATIVE(filename)) {
-               strlcpy(dir, filename, sizeof(dir));
-               p = strrchr(dir, FR_DIR_SEP);
-               if (p) {
-                       p[1] = '\0';
-               } else {
-                       strlcat(dir, "/", sizeof(dir));
-               }
+       return da_p;
+}
 
-               strlcpy(fn, filename, sizeof(fn));
-       } else {
-               strlcpy(dir, dir_name, sizeof(dir));
-               p = strrchr(dir, FR_DIR_SEP);
-               if (p) {
-                       if (p[1]) strlcat(dir, "/", sizeof(dir));
-               } else {
-                       strlcat(dir, "/", sizeof(dir));
-               }
-               strlcat(dir, filename, sizeof(dir));
-               p = strrchr(dir, FR_DIR_SEP);
-               if (p) {
-                       p[1] = '\0';
-               } else {
-                       strlcat(dir, "/", sizeof(dir));
-               }
+/** Return vendor attribute for the specified dictionary and vendorpec
+ *
+ * @param[in] dict             to search for the vendor in.
+ * @param[in] vendor_root      of the vendor root attribute.  Could be 26 (for example) in RADIUS.
+ * @param[in] vendor           to find.
+ * @return
+ *     - NULL if vendor does not exist.
+ *     - A fr_dict_attr_t representing the vendor in the dictionary hierarchy.
+ */
+fr_dict_attr_t const *fr_dict_vendor_attr_by_num(fr_dict_t const *dict, unsigned int vendor_root, unsigned int vendor)
+{
+       fr_dict_attr_t const *da;
 
-               p = strrchr(filename, FR_DIR_SEP);
-               if (p) {
-                       snprintf(fn, sizeof(fn), "%s%s", dir, p);
-               } else {
-                       snprintf(fn, sizeof(fn), "%s%s", dir, filename);
-               }
-       }
+       if (!dict) return NULL;
 
-       /*
-        *      Check if we've loaded this file before.  If so, ignore it.
-        */
-       p = strrchr(fn, FR_DIR_SEP);
-       if (p) {
-               *p = '\0';
-               if (dict_stat_check(ctx->dict, fn, p + 1)) {
-                       *p = FR_DIR_SEP;
-                       return 0;
-               }
-               *p = FR_DIR_SEP;
+       da = fr_dict_attr_child_by_num(fr_dict_root(dict), vendor_root);
+       if (!da) {
+               fr_strerror_printf("Vendor root attribute %i not defined in dict %s", vendor_root, dict->root->name);
+               return NULL;
        }
 
-       if ((fp = fopen(fn, "r")) == NULL) {
-               if (!src_file) {
-                       fr_strerror_printf_push("%s: Couldn't open dictionary '%s': %s",
-                                          "Error reading dictionary", fn, fr_syserror(errno));
-               } else {
-                       fr_strerror_printf_push("%s: %s[%d]: Couldn't open dictionary '%s': %s",
-                                          "Error reading dictionary", src_file, src_line, fn, fr_syserror(errno));
-               }
-               return -2;
-       }
+       switch (da->type) {
+       case FR_TYPE_VSA:       /* Vendor specific attribute */
+       case FR_TYPE_EVS:       /* Extended vendor specific attribute */
+               break;
 
-       /*
-        *      If fopen works, this works.
-        */
-       if (stat(fn, &statbuf) < 0) {
-               fclose(fp);
-               return -1;
+       default:
+               fr_strerror_printf("Wrong type for vendor root, expected '%s' or '%s' got '%s'",
+                                  fr_int2str(dict_attr_types, FR_TYPE_VSA, "<INVALID>"),
+                                  fr_int2str(dict_attr_types, FR_TYPE_EVS, "<INVALID>"),
+                                  fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+               return NULL;
        }
 
-       if (!S_ISREG(statbuf.st_mode)) {
-               fclose(fp);
-               fr_strerror_printf_push("%s: Dictionary '%s' is not a regular file", "Error reading dictionary", fn);
-               return -1;
+       da = fr_dict_attr_child_by_num(da, vendor);
+       if (!da) {
+               fr_strerror_printf("Vendor %i not defined", vendor);
+               return NULL;
        }
 
-       /*
-        *      Globally writable dictionaries means that users can control
-        *      the server configuration with little difficulty.
-        */
-#ifdef S_IWOTH
-       if ((statbuf.st_mode & S_IWOTH) != 0) {
-               fclose(fp);
-               fr_strerror_printf_push("%s: Dictionary '%s' is globally writable.  Refusing to start "
-                                  "due to insecure configuration", "Error reading dictionary", fn);
-               return -1;
+       if (da->type != FR_TYPE_VENDOR) {
+               fr_strerror_printf("Wrong type for vendor, expected '%s' got '%s'",
+                                  fr_int2str(dict_attr_types, da->type, "<INVALID>"),
+                                  fr_int2str(dict_attr_types, FR_TYPE_VENDOR, "<INVALID>"));
+               return NULL;
        }
-#endif
-
-       dict_stat_add(ctx->dict, &statbuf);
 
-       /*
-        *      Seed the random pool with data.
-        */
-       fr_rand_seed(&statbuf, sizeof(statbuf));
+       return da;
+}
 
-       memset(&base_flags, 0, sizeof(base_flags));
-
-       while (fgets(buf, sizeof(buf), fp) != NULL) {
-               line++;
-
-               switch (buf[0]) {
-               case '#':
-               case '\0':
-               case '\n':
-               case '\r':
-                       continue;
-               }
-
-               /*
-                *  Comment characters should NOT be appearing anywhere but
-                *  as start of a comment;
-                */
-               p = strchr(buf, '#');
-               if (p) *p = '\0';
-
-               argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
-               if (argc == 0) continue;
+/** Look up a dictionary attribute by a name embedded in another string
+ *
+ * Find the first invalid attribute name char in the string pointed
+ * to by name.
+ *
+ * Copy the characters between the start of the name string and the first
+ * none #fr_dict_attr_allowed_chars char to a buffer and perform a dictionary lookup
+ * using that value.
+ *
+ * If the attribute exists, advance the pointer pointed to by name
+ * to the first none #fr_dict_attr_allowed_chars char, and return the DA.
+ *
+ * If the attribute does not exist, don't advance the pointer and return
+ * NULL.
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in,out] name         string start.
+ * @return
+ *     - Attribute matching name.
+ *     - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_name_substr(fr_dict_t const *dict, char const **name)
+{
+       fr_dict_attr_t          find;
+       fr_dict_attr_t const    *da;
+       char const              *p;
+       size_t                  len;
 
-               if (argc == 1) {
-                       fr_strerror_printf("Invalid entry");
+       if (!name || !*name) return NULL;
+       INTERNAL_IF_NULL(dict);
 
-               error:
-                       fr_strerror_printf_push("Error reading %s[%d]", fn, line);
-                       fclose(fp);
-                       return -1;
-               }
+       memset(&find, 0, sizeof(find));
 
-               /*
-                *      Process VALUE lines.
-                */
-               if (strcasecmp(argv[0], "VALUE") == 0) {
-                       if (dict_read_process_value(ctx->dict, argv + 1, argc - 1) == -1) goto error;
-                       continue;
-               }
+       /*
+        *      Advance p until we get something that's not part of
+        *      the dictionary attribute name.
+        */
+       for (p = *name; fr_dict_attr_allowed_chars[(int)*p]; p++);
 
-               /*
-                *      Perhaps this is an attribute.
-                */
-               if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
-                       if (!base_flags.named) {
-                               if (dict_read_process_attribute(ctx->dict, ctx->parent, ctx->block_vendor,
-                                                               argv + 1, argc - 1,
-                                                               &base_flags) == -1) goto error;
-                       } else {
-                               if (dict_read_process_named_attribute(ctx->dict, ctx->parent,
-                                                                     argv + 1, argc - 1,
-                                                                     &base_flags) == -1) goto error;
-                       }
-                       continue;
-               }
+       len = p - *name;
+       if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
+               fr_strerror_printf("Attribute name too long");
+               return NULL;
+       }
 
-               /*
-                *      Process VALUE lines.
-                */
-               if (strcasecmp(argv[0], "FLAGS") == 0) {
-                       if (dict_read_process_flags(ctx->dict, argv + 1, argc - 1, &base_flags) == -1) goto error;
-                       continue;
-               }
+       find.name = talloc_bstrndup(NULL, *name, len);
+       if (!find.name) {
+               fr_strerror_printf("Out of memory");
+               return NULL;
+       }
+       da = fr_hash_table_finddata(dict->attributes_by_name, &find);
+       talloc_const_free(find.name);
 
-               /*
-                *      See if we need to import another dictionary.
-                */
-               if (strcasecmp(argv[0], "$INCLUDE") == 0) {
-                       dict_from_file_ctx_t nctx = *ctx;
+       if (!da) {
+               fr_strerror_printf("Unknown attribute '%.*s'", (int) len, *name);
+               return NULL;
+       }
+       *name = p;
 
-                       /*
-                        *      Included files operate on a copy of the context
-                        */
-                       if (_dict_from_file(&nctx, dir, argv[1], fn, line) < 0) {
-                               fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
-                               fclose(fp);
-                               return -1;
-                       }
-                       continue;
-               } /* $INCLUDE */
+       return da;
+}
 
-               /*
-                *      Optionally include a dictionary
-                */
-               if (strcasecmp(argv[0], "$INCLUDE-") == 0) {
-                       int rcode = _dict_from_file(ctx, dir, argv[1], fn, line);
+/** Locate a #fr_dict_attr_t by its name
+ *
+ * @note Unlike attribute numbers, attribute names are unique to the dictionary.
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] name             of the attribute to locate.
+ * @return
+ *     - Attribute matching name.
+ *     - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_name(fr_dict_t const *dict, char const *name)
+{
+       fr_dict_attr_t find = { .name = name };
 
-                       if (rcode == -2) {
-                               fr_strerror_printf(NULL); /* delete all errors */
-                               continue;
-                       }
+       if (!name) return NULL;
+       INTERNAL_IF_NULL(dict);
 
-                       if (rcode < 0) {
-                               fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
-                               fclose(fp);
-                               return -1;
-                       }
-                       continue;
-               } /* $INCLUDE- */
+       return fr_hash_table_finddata(dict->attributes_by_name, &find);
+}
 
-               /*
-                *      Process VENDOR lines.
-                */
-               if (strcasecmp(argv[0], "VENDOR") == 0) {
-                       if (dict_read_process_vendor(ctx->dict, argv + 1, argc - 1) == -1) goto error;
-                       continue;
-               }
+/** Lookup a #fr_dict_attr_t by its vendor and attribute numbers
+ *
+ * @note This is a deprecated function, new code should use #fr_dict_attr_child_by_num.
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] vendor           number of the attribute.
+ * @param[in] attr             number of the attribute.
+ * @return
+ *     - Attribute matching vendor/attr.
+ *     - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
+{
+       fr_dict_attr_t const *parent;
 
-               /*
-                *      Process PROTOCOL line.  Defines a new protocol.
-                */
-               if (strcasecmp(argv[0], "PROTOCOL") == 0) {
-                       if (argc < 2) {
-                               fr_strerror_printf("Invalid PROTOCOL entry");
-                               goto error;
-                       }
-                       if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error;
-                       continue;
-               }
+       INTERNAL_IF_NULL(dict);
 
-               /*
-                *      Switches the current protocol context
-                */
-               if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) {
-                       fr_dict_t *found;
+       if (vendor == 0) return fr_dict_attr_child_by_num(dict->root, attr);
 
-                       ctx->old_dict = ctx->dict;
+       parent = fr_dict_attr_child_by_num(dict->root, FR_VENDOR_SPECIFIC);
+       if (!parent) return NULL;
 
-                       if (argc != 2) {
-                               fr_strerror_printf("Invalid BEGIN-PROTOCOL entry");
-                               goto error;
-                       }
+       parent = fr_dict_attr_child_by_num(parent, vendor);
+       if (!parent) return NULL;
 
-                       found = fr_dict_by_protocol_name(argv[1]);
-                       if (!found) {
-                               fr_strerror_printf("Unknown protocol '%s'", argv[1]);
-                               goto error;
-                       }
+       return fr_dict_attr_child_by_num(parent, attr);
+}
 
-                       ctx->dict = found;
+/** Lookup a attribute by its its vendor and attribute numbers and data type
+ *
+ * @note Only works with FR_TYPE_COMBO_IP
+ *
+ * @param[in] da               to look for type variant of.
+ * @param[in] type             Variant of attribute to lookup.
+ * @return
+ *     - Attribute matching parent/attr/type.
+ *     - NULL if no matching attribute could be found.
+ */
+fr_dict_attr_t const *fr_dict_attr_by_type(fr_dict_attr_t const *da, fr_type_t type)
+{
+       fr_dict_t       *dict = fr_dict_by_da(da);
+       fr_dict_attr_t  find = {
+                               .parent = da->parent,
+                               .attr = da->attr,
+                               .type = type
+                       };
 
-                       continue;
-               }
+       return fr_hash_table_finddata(dict->attributes_combo, &find);
+}
 
-               /*
-                *      Switches back to the previous protocol context
-                */
-               if (strcasecmp(argv[0], "END-PROTOCOL") == 0) {
-                       fr_dict_t const *found;
+/** Check if a child attribute exists in a parent using a pointer (da)
+ *
+ * @param[in] parent           to check for child in.
+ * @param[in] child            to look for.
+ * @return
+ *     - The child attribute on success.
+ *     - NULL if the child attribute does not exist.
+ */
+inline fr_dict_attr_t const *fr_dict_attr_child_by_da(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
+{
+       fr_dict_attr_t const *bin;
 
-                       if (argc != 2) {
-                               fr_strerror_printf("Invalid END-PROTOCOL entry");
-                               goto error;
-                       }
+       VERIFY_DA(parent);
 
-                       found = fr_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 (!parent->children) return NULL;
 
-                       if (found != ctx->dict) {
-                               fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
-                                                  argv[1], found->root->name);
-                               goto error;
-                       }
+       /*
+        *      Only some types can have children
+        */
+       switch (parent->type) {
+       default:
+               return NULL;
 
-                       ctx->dict = ctx->old_dict;      /* Switch back to the old dictionary */
+       case FR_TYPE_STRUCTURAL:
+               break;
+       }
 
-                       continue;
-               }
+       /*
+        *      Child arrays may be trimmed back to save memory.
+        *      Check that so we don't SEGV.
+        */
+       if ((child->attr & 0xff) > talloc_array_length(parent->children)) return NULL;
 
-               /*
-                *      Switches TLV parent context
-                */
-               if (strcasecmp(argv[0], "BEGIN-TLV") == 0) {
-                       fr_dict_attr_t const *common;
+       bin = parent->children[child->attr & 0xff];
+       for (;;) {
+               if (!bin) return NULL;
+               if (bin == child) return bin;
+               bin = bin->next;
+       }
 
-                       if ((ctx->block_tlv_depth + 1) > FR_DICT_TLV_NEST_MAX) {
-                               fr_strerror_printf_push("TLVs are nested too deep");
-                               goto error;
-                       }
+       return NULL;
+}
 
-                       if (argc != 2) {
-                               fr_strerror_printf_push("Invalid BEGIN-TLV entry");
-                               goto error;
-                       }
+/** Check if a child attribute exists in a parent using an attribute number
+ *
+ * @param[in] parent           to check for child in.
+ * @param[in] attr             number to look for.
+ * @return
+ *     - The child attribute on success.
+ *     - NULL if the child attribute does not exist.
+ */
+inline fr_dict_attr_t const *fr_dict_attr_child_by_num(fr_dict_attr_t const *parent, unsigned int attr)
+{
+       fr_dict_attr_t const *bin;
 
-                       da = fr_dict_attr_by_name(ctx->dict, argv[1]);
-                       if (!da) {
-                               fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
-                               goto error;
-                       }
+       VERIFY_DA(parent);
 
-                       if (da->type != FR_TYPE_TLV) {
-                               fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
-                                                       argv[1],
-                                                       fr_int2str(dict_attr_types, da->type, "?Unknown?"));
-                               goto error;
-                       }
+       if (!parent->children) return NULL;
 
-                       common = fr_dict_parent_common(ctx->parent, da, true);
-                       if (!common ||
-                           (common->type == FR_TYPE_VSA) ||
-                           (common->type == FR_TYPE_EVS)) {
-                               fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
-                                                       argv[1], ctx->parent->name);
-                               goto error;
-                       }
+       /*
+        *      Only some types can have children
+        */
+       switch (parent->type) {
+       default:
+               return NULL;
 
-                       ctx->block_tlv[ctx->block_tlv_depth++] = ctx->parent;
-                       ctx->parent = da;
+       case FR_TYPE_STRUCTURAL:
+               break;
+       }
 
-                       continue;
-               } /* BEGIN-TLV */
+       /*
+        *      Child arrays may be trimmed back to save memory.
+        *      Check that so we don't SEGV.
+        */
+       if ((attr & 0xff) > talloc_array_length(parent->children)) return NULL;
 
-               /*
-                *      Switches back to previous TLV parent
-                */
-               if (strcasecmp(argv[0], "END-TLV") == 0) {
-                       if (--ctx->block_tlv_depth < 0) {
-                               fr_strerror_printf_push("Too many END-TLV entries.  Mismatch at END-TLV %s", argv[1]);
-                               goto error;
-                       }
+       bin = parent->children[attr & 0xff];
+       for (;;) {
+               if (!bin) return NULL;
+               if (bin->attr == attr) return bin;
+               bin = bin->next;
+       }
 
-                       if (argc != 2) {
-                               fr_strerror_printf_push("Invalid END-TLV entry");
-                               goto error;
-                       }
+       return NULL;
+}
 
-                       da = fr_dict_attr_by_name(ctx->dict, argv[1]);
-                       if (!da) {
-                               fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
-                               goto error;
-                       }
+/** Lookup the structure representing an enum value in a #fr_dict_attr_t
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] da               to search in.
+ * @param[in] value            to search for.
+ * @return
+ *     - Matching #fr_dict_enum_t.
+ *     - NULL if no matching #fr_dict_enum_t could be found.
+ */
+fr_dict_enum_t *fr_dict_enum_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+{
+       fr_dict_enum_t enumv, *dv;
 
-                       if (da != ctx->parent) {
-                               fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1],
-                                                  ctx->parent->name);
-                               goto error;
-                       }
-                       ctx->parent = ctx->block_tlv[ctx->block_tlv_depth];
-                       continue;
-               } /* END-VENDOR */
+       if (!da) return NULL;
 
-               if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) {
-                       unsigned int            vendor;
-                       fr_dict_attr_flags_t    flags;
+       INTERNAL_IF_NULL(dict);
 
-                       fr_dict_attr_t const    *vsa_da;
-                       fr_dict_attr_t const    *vendor_da;
-                       fr_dict_attr_t          *new;
-                       fr_dict_attr_t          *mutable;
+       /*
+        *      Could be NULL or an unknown attribute, in which case
+        *      we want to avoid the lookup gracefully...
+        */
+       if (value->type != da->type) return NULL;
 
-                       if (argc < 2) {
-                               fr_strerror_printf_push("Invalid BEGIN-VENDOR entry");
-                               goto error;
-                       }
+       /*
+        *      First, look up aliases.
+        */
+       enumv.da = da;
+       enumv.alias = "";
 
-                       vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
-                       if (!vendor) {
-                               fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
-                               goto error;
-                       }
+       /*
+        *      Look up the attribute alias target, and use
+        *      the correct attribute number if found.
+        */
+       dv = fr_hash_table_finddata(dict->values_by_alias, &enumv);
+       if (dv) enumv.da = dv->da;
 
-                       /*
-                        *      Check for extended attr VSAs
-                        *
-                        *      BEGIN-VENDOR foo format=Foo-Encapsulation-Attr
-                        */
-                       if (argc > 2) {
-                               if (strncmp(argv[2], "format=", 7) != 0) {
-                                       fr_strerror_printf_push("Invalid format %s", argv[2]);
-                                       goto error;
-                               }
+       enumv.value = value;
 
-                               p = argv[2] + 7;
-                               da = fr_dict_attr_by_name(ctx->dict, p);
-                               if (!da) {
-                                       fr_strerror_printf_push("Invalid format for BEGIN-VENDOR: Unknown "
-                                                               "attribute '%s'", p);
-                                       goto error;
-                               }
+       return fr_hash_table_finddata(dict->values_by_da, &enumv);
+}
 
-                               if (da->type != FR_TYPE_EVS) {
-                                       fr_strerror_printf_push("Invalid format for BEGIN-VENDOR.  "
-                                                               "Attribute '%s' should be 'evs' but is '%s'", p,
-                                                               fr_int2str(dict_attr_types, da->type, "?Unknown?"));
-                                       goto error;
-                               }
+/** Lookup the name of an enum value in a #fr_dict_attr_t
+ *
+ * @param[in] dict             of protocol context we're operating in.
+ *                             If NULL the internal dictionary will be used.
+ * @param[in] da               to search in.
+ * @param[in] value            number to search for.
+ * @return
+ *     - Name of value.
+ *     - NULL if no matching value could be found.
+ */
+char const *fr_dict_enum_alias_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+{
+       fr_dict_enum_t *dv;
 
-                               vsa_da = da;
-                       } else {
-                               /*
-                                *      Automagically create Attribute 26
-                                *
-                                *      This should exist, but in case we're starting without
-                                *      the RFC dictionaries we need to add it in the case
-                                *      it doesn't.
-                                */
-                               vsa_da = fr_dict_attr_child_by_num(ctx->parent, FR_VENDOR_SPECIFIC);
-                               if (!vsa_da) {
-                                       memset(&flags, 0, sizeof(flags));
+       if (!da) return NULL;
 
-                                       memcpy(&mutable, &ctx->parent, sizeof(mutable));
-                                       new = dict_attr_alloc(mutable, fr_dict_root(ctx->dict), "Vendor-Specific",
-                                                                FR_VENDOR_SPECIFIC, FR_TYPE_VSA, &flags);
-                                       dict_attr_child_add(mutable, new);
-                                       vsa_da = new;
-                               }
-                       }
+       INTERNAL_IF_NULL(dict);
 
-                       /*
-                        *      Create a VENDOR attribute on the fly, either in the context
-                        *      of the EVS attribute, or the VSA (26) attribute.
-                        */
-                       vendor_da = fr_dict_attr_child_by_num(vsa_da, vendor);
-                       if (!vendor_da) {
-                               memset(&flags, 0, sizeof(flags));
+       dv = fr_dict_enum_by_value(dict, da, value);
+       if (!dv) return "";
 
-                               if (vsa_da->type == FR_TYPE_VSA) {
-                                       fr_dict_vendor_t const *dv;
+       return dv->alias;
+}
 
-                                       dv = fr_dict_vendor_by_num(ctx->dict, vendor);
-                                       if (dv) {
-                                               flags.type_size = dv->type;
-                                               flags.length = dv->length;
+/*
+ *     Get a value by its name, keyed off of an attribute.
+ */
+fr_dict_enum_t *fr_dict_enum_by_alias(fr_dict_t *dict, fr_dict_attr_t const *da, char const *alias)
+{
+       fr_dict_enum_t find, *found;
 
-                                       } else { /* unknown vendor, shouldn't happen */
-                                               flags.type_size = 1;
-                                               flags.length = 1;
-                                       }
+       memset(&find, 0, sizeof(find));
 
-                               } else { /* EVS are always "format=1,1" */
-                                       flags.type_size = 1;
-                                       flags.length = 1;
-                               }
+       if (!alias) return NULL;
 
-                               memcpy(&mutable, &vsa_da, sizeof(mutable));
-                               new = dict_attr_alloc(mutable, ctx->parent, argv[1], vendor, FR_TYPE_VENDOR, &flags);
-                               dict_attr_child_add(mutable, new);
+       INTERNAL_IF_NULL(dict);
 
-                               vendor_da = new;
-                       }
-                       ctx->parent = vendor_da;
-                       ctx->block_vendor = vendor;
-                       continue;
-               } /* BEGIN-VENDOR */
+       find.da = da;
+       find.alias = alias;
 
-               if (strcasecmp(argv[0], "END-VENDOR") == 0) {
-                       unsigned int vendor;
+       /*
+        *      Look up the attribute alias target, and use
+        *      the correct attribute number if found.
+        */
+       found = fr_hash_table_finddata(dict->values_by_alias, &find);
+       if (found) find.da = found->da;
 
-                       if (argc != 2) {
-                               fr_strerror_printf_push("Invalid END-VENDOR entry");
-                               goto error;
-                       }
+       return fr_hash_table_finddata(dict->values_by_alias, &find);
+}
 
-                       vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
-                       if (!vendor) {
-                               fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
-                               goto error;
-                       }
+/*
+ *     String split routine.  Splits an input string IN PLACE
+ *     into pieces, based on spaces.
+ */
+int fr_dict_str_to_argv(char *str, char **argv, int max_argc)
+{
+       int argc = 0;
 
-                       if (vendor != ctx->block_vendor) {
-                               fr_strerror_printf_push("END-VENDOR '%s' does not match any previous BEGIN-VENDOR",
-                                                  argv[1]);
-                               goto error;
-                       }
-                       ctx->parent = ctx->dict->root;
-                       ctx->block_vendor = 0;
-                       continue;
-               } /* END-VENDOR */
+       while (*str) {
+               if (argc >= max_argc) break;
 
                /*
-                *      Any other string: We don't recognize it.
+                *      Chop out comments early.
                 */
-               fr_strerror_printf_push("Invalid keyword '%s'", argv[0]);
-               goto error;
-       }
-       fclose(fp);
-       return 0;
-}
+               if (*str == '#') {
+                       *str = '\0';
+                       break;
+               }
 
-static int dict_from_file(fr_dict_t *dict,
-                         char const *dir_name, char const *filename,
-                         char const *src_file, int src_line)
-{
-       dict_from_file_ctx_t    ctx = {
-                                       .dict = dict,
-                                       .parent = dict->root
-                               };
+               while ((*str == ' ') ||
+                      (*str == '\t') ||
+                      (*str == '\r') ||
+                      (*str == '\n'))
+                       *(str++) = '\0';
 
-       return _dict_from_file(&ctx, dir_name, filename, src_file, src_line);
-}
+               if (!*str) break;
 
-/*
- *     External API for testing
- */
-int fr_dict_parse_str(fr_dict_t *dict, char *buf, fr_dict_attr_t const *parent, unsigned int vendor)
-{
-       int     argc;
-       char    *argv[MAX_ARGV];
-       fr_dict_attr_flags_t base_flags;
+               argv[argc] = str;
+               argc++;
+
+               while (*str &&
+                      (*str != ' ') &&
+                      (*str != '\t') &&
+                      (*str != '\r') &&
+                      (*str != '\n'))
+                       str++;
+       }
 
-       INTERNAL_IF_NULL(dict);
+       return argc;
+}
 
-       argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
-       if (argc == 0) return 0;
+static int dict_read_sscanf_i(unsigned int *pvalue, char const *str)
+{
+       int rcode = 0;
+       int base = 10;
+       static char const *tab = "0123456789";
 
-       if (strcasecmp(argv[0], "VALUE") == 0) {
-               return dict_read_process_value(dict, argv + 1, argc - 1);
+       if ((str[0] == '0') &&
+           ((str[1] == 'x') || (str[1] == 'X'))) {
+               tab = "0123456789abcdef";
+               base = 16;
+
+               str += 2;
        }
 
-       if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
-               if (!parent) parent = fr_dict_root(dict);
+       while (*str) {
+               char const *c;
 
-               memset(&base_flags, 0, sizeof(base_flags));
+               if (*str == '.') break;
 
-               return dict_read_process_attribute(dict, parent, vendor, argv + 1, argc - 1, &base_flags);
-       }
+               c = memchr(tab, tolower((int)*str), base);
+               if (!c) return 0;
 
-       if (strcasecmp(argv[0], "VENDOR") == 0) {
-               return dict_read_process_vendor(dict, argv + 1, argc - 1);
+               rcode *= base;
+               rcode += (c - tab);
+               str++;
        }
 
-       fr_strerror_printf("Invalid input '%s'", argv[0]);
-       return -1;
+       *pvalue = rcode;
+       return 1;
 }
 
-/** Return the root attribute of a dictionary
+/** Parser context for dict_from_file
  *
- * @param dict                 to return root for.
- * @return the root attribute of the dictionary.
+ * Allows vendor and TLV context to persist across $INCLUDEs
  */
-fr_dict_attr_t const *fr_dict_root(fr_dict_t const *dict)
+typedef struct {
+       fr_dict_t               *dict;                  //!< Protocol dictionary we're inserting attributes into.
+       fr_dict_t               *old_dict;              //!< The dictionary before the current BEGIN-PROTOCOL block.
+
+       unsigned int            block_vendor;           //!< Vendor block we're inserting attributes into.
+                                                       //!< Can be removed once we remove the vendor field from
+                                                       //!< #fr_dict_attr_t.
+
+       fr_dict_attr_t const    *block_tlv[FR_DICT_TLV_NEST_MAX];       //!< Nested TLV block's we're
+                                                                       //!< inserting attributes into.
+       int                     block_tlv_depth;        //!< Nested TLV block index we're inserting into.
+
+       fr_dict_attr_t const    *parent;                //!< Current parent attribute (root/vendor/tlv).
+} dict_from_file_ctx_t;
+
+/** Initialise the global protocol hashes
+ *
+ * @note Must be called before any other dictionary functions.
+ *
+ *
+ * @param[in] ctx to allocate the hashes in.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int dict_global_init(TALLOC_CTX *ctx)
 {
-       return dict->root;
+       if (protocol_by_name && protocol_by_num) return 0;
+
+       protocol_by_name = fr_hash_table_create(ctx, dict_protocol_name_hash, dict_protocol_name_cmp, NULL);
+       if (!protocol_by_name) {
+               fr_strerror_printf("Failed initializing protocol_by_name hash");
+               return -1;
+       }
+       protocol_by_num = fr_hash_table_create(ctx, dict_protocol_num_hash, dict_protocol_num_cmp, NULL);
+       if (!protocol_by_num) {
+               fr_strerror_printf("Failed initializing protocol_by_num hash");
+               return -1;
+       }
+
+       return 0;
 }
 
-/** Copy a known or unknown attribute to produce an unknown attribute
+/** Set a new root dictionary attribute
  *
- * Will copy the complete hierarchy down to the first known attribute.
+ * @note Must only be called once per dictionary.
+ *
+ * @param[in] dict             to modify.
+ * @param[in] name             of dictionary root.
+ * @param[in] protocol_number  The artificial (or IANA allocated) number for the protocol.
+ *                             This is only used for
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-fr_dict_attr_t *fr_dict_unknown_acopy(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
+static int dict_root_set(fr_dict_t *dict, char const *name, unsigned int proto_number)
 {
-       fr_dict_attr_t *n, *new_parent = NULL;
-       fr_dict_attr_t const *parent;
+       fr_dict_attr_flags_t flags = {
+               .is_root = 1,
+               .type_size = 1,
+               .length = 1
+       };
 
-       if (da->parent->flags.is_unknown) {
-               new_parent = fr_dict_unknown_acopy(ctx, da->parent);
-               parent = new_parent;
-       } else {
-               parent = da->parent;
+       if (!fr_cond_assert(!dict->root)) {
+               fr_strerror_printf("Dictionary root already set");
+               return -1;
        }
 
-       n = dict_attr_alloc(ctx, parent, da->name, da->attr, da->type, &da->flags);
-       n->parent = parent;
-       n->depth = da->depth;
+       dict->root = dict_attr_alloc_name(dict, name);
+       if (!dict->root) return -1;
 
-       /*
-        *      Inverted tallloc hierarchy.
-        */
-       if (new_parent) talloc_steal(n, parent);
+       dict_attr_init(dict->root, NULL, proto_number, FR_TYPE_TLV, &flags);
+       VERIFY_DA(dict->root);
 
-       return n;
+       return 0;
 }
 
-/** Converts an unknown to a known by adding it to the internal dictionaries.
- *
- * Does not free old #fr_dict_attr_t, that is left up to the caller.
+/** Allocate a new dictionary
  *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] old              unknown attribute to add.
+ * @param[in] ctx to allocate dictionary in.
  * @return
- *     - Existing #fr_dict_attr_t if old was found in a dictionary.
- *     - A new entry representing old.
+ *     - NULL on memory allocation error.
  */
-fr_dict_attr_t const *fr_dict_unknown_add(fr_dict_t *dict, fr_dict_attr_t const *old)
+static fr_dict_t *dict_alloc(TALLOC_CTX *ctx)
 {
-       fr_dict_attr_t const *da;
-       fr_dict_attr_t const *parent;
-       fr_dict_attr_flags_t flags;
+       fr_dict_t *dict;
 
-       if (!old) return NULL;
+       dict = talloc_zero(ctx, fr_dict_t);
+       if (!dict) {
+       error:
+               fr_strerror_printf("Failed allocating memory for dictionary");
+               talloc_free(dict);
+               return NULL;
+       }
 
-       da = fr_dict_attr_by_name(dict, old->name);
-       if (da) return da;
+       /*
+        *      Pre-Allocate 5MB of pool memory for rapid startup
+        */
+       dict->pool = talloc_pool(dict, (1024 * 1024 * 5));
+       if (!dict->pool) goto error;
 
        /*
-        *      Define the complete unknown hierarchy
+        *      Create the table of vendor by name.   There MAY NOT
+        *      be multiple vendors of the same name.
         */
-       if (old->parent && old->parent->flags.is_unknown) {
-               parent = fr_dict_unknown_add(dict, old->parent);
-               if (!parent) {
-                       fr_strerror_printf_push("Failed adding parent \"%s\"", old->parent->name);
-                       return NULL;
-               }
-       } else {
-               parent = old->parent;
-       }
+       dict->vendors_by_name = fr_hash_table_create(dict, dict_vendor_name_hash, dict_vendor_name_cmp, hash_pool_free);
+       if (!dict->vendors_by_name) goto error;
 
-       memcpy(&flags, &old->flags, sizeof(flags));
-       flags.is_unknown = false;
-       flags.is_raw = true;
+       /*
+        *      Create the table of vendors by value.  There MAY
+        *      be vendors of the same value.  If there are, we
+        *      pick the latest one.
+        */
+       dict->vendors_by_num = fr_hash_table_create(dict, dict_vendor_vendorpec_hash, dict_vendor_vendorpec_cmp, NULL);
+       if (!dict->vendors_by_num) goto error;
 
        /*
-        *      If this is a vendor, we skip most of the sanity
-        *      checks and add it to the vendor hash, and add it
-        *      as a child attribute to the Vendor-Specific
-        *      container.
+        *      Create the table of attributes by name.   There MAY NOT
+        *      be multiple attributes of the same name.
         */
-       if (old->type == FR_TYPE_VENDOR) {
-               fr_dict_attr_t *mutable, *n;
+       dict->attributes_by_name = fr_hash_table_create(dict, dict_attr_name_hash, dict_attr_name_cmp, NULL);
+       if (!dict->attributes_by_name) goto error;
 
-               if (dict_vendor_add(dict, old->name, old->attr) < 0) return NULL;
+       /*
+        *      Horrible hacks for combo-IP.
+        */
+       dict->attributes_combo = fr_hash_table_create(dict, dict_attr_combo_hash, dict_attr_combo_cmp, hash_pool_free);
+       if (!dict->attributes_combo) goto error;
 
-               n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
+       dict->values_by_alias = fr_hash_table_create(dict, dict_enum_alias_hash, dict_enum_alias_cmp, hash_pool_free);
+       if (!dict->values_by_alias) goto error;
 
-               /*
-                *      Setup parenting for the attribute
-                */
-               memcpy(&mutable, &old->parent, sizeof(mutable));
-               if (dict_attr_child_add(mutable, n) < 0) return NULL;
+       dict->values_by_da = fr_hash_table_create(dict, dict_enum_value_hash, dict_enum_value_cmp, hash_pool_free);
+       if (!dict->values_by_da) goto error;
 
-               return n;
-       }
+       return dict;
+}
+
+/** Lookup a dictionary reference
+ *
+ * Format is [<proto>].[<attr>]
+ *
+ * If protocol is omitted lookup is in the current dictionary.
+ *
+ * FIXME: Probably needs the dictionary equivalent of pass2, to fixup circular dependencies
+ *     DHCPv4->RADIUS and RADIUS->DHCPv4 are both valid.
+ *
+ * @param[in] dict     The current dictionary we're parsing.
+ * @param[in,out] ref  The reference string.  Pointer advanced to the end of the string.
+ * @return
+ *     - NULL if the reference is invalid.
+ *     - A local or foreign attribute representing the target of the reference.
+ */
+static fr_dict_attr_t const *dict_resolve_reference(fr_dict_t *dict, char const *ref)
+{
+       char const              *p = ref, *q, *end = p + strlen(ref);
+       fr_dict_t               *proto_dict;
+       fr_dict_attr_t const    *da;
 
        /*
-        *      Look up the attribute by number.  If it doesn't exist,
-        *      add it both by name and by number.  If it does exist,
-        *      add it only by name.
+        *      If the reference does not begin with .
+        *      then it's a reference into a foreign
+        *      protocol.
         */
-       da = fr_dict_attr_child_by_num(parent, old->attr);
-       if (da) {
-               fr_dict_attr_t *n;
+       if (*p != '.') {
+               char buffer[FR_DICT_PROTO_MAX_NAME_LEN + 1];
 
-               n = dict_attr_alloc(dict->pool, parent, old->name, old->attr, old->type, &flags);
-               if (!n) return NULL;
+               q = strchr(p, '.');
+               if (!q) q = end;
 
-               /*
-                *      Add the unknown by NAME.  e.g. if the admin does "Attr-26", we want
-                *      to return "Attr-26", and NOT "Vendor-Specific".  The rest of the server
-                *      is responsible for converting "Attr-26 = 0x..." to an actual attribute,
-                *      if it so desires.
-                */
-               if (dict_attr_add_by_name(dict, n) < 0) {
-                       talloc_free(n);
+               if ((size_t)(q - p) > sizeof(buffer)) {
+                       fr_strerror_printf("Protocol name too long");
                        return NULL;
                }
 
-               return n;
-       }
+               strlcpy(buffer, p, (q - p + 1));
+               p = q;
+
+               dict = fr_dict_by_protocol_name(buffer);
+               if (!dict) {
+                       fr_strerror_printf("Referenced protocol \"%s\" not found", buffer);
+                       return NULL;
+               }
 
+               return NULL;
        /*
-        *      Add the attribute by both name and number.
+        *      If the reference string begins with .
+        *      then the reference is in the current
+        *      dictionary.
         */
-       if (fr_dict_attr_add(dict, parent, old->name, old->attr, old->type, &flags) < 0) return NULL;
+       } else {
+               proto_dict = dict;
+       }
 
        /*
-        *      For paranoia, return it by name.
+        *      If there's a '.' after the dictionary, then
+        *      the reference is to a specific attribute.
         */
-       return fr_dict_attr_by_name(dict, old->name);
-}
-
-/** Free dynamically allocated (unknown attributes)
- *
- * If the da was dynamically allocated it will be freed, else the function
- * will return without doing anything.
- *
- * @param[in] da to free.
- */
-void fr_dict_unknown_free(fr_dict_attr_t const **da)
-{
-       fr_dict_attr_t **tmp;
-
-       if (!da || !*da) return;
+       if (*p == '.') {
+               p++;
 
-       /* Don't free real DAs */
-       if (!(*da)->flags.is_unknown) {
-               return;
+               da = fr_dict_attr_by_name_substr(proto_dict, &p);
+               if (!da) {
+                       fr_strerror_printf("Referenced attribute \"%s\" not found", p);
+                       return NULL;
+               }
        }
 
-       memcpy(&tmp, &da, sizeof(*tmp));
-       talloc_free(*tmp);
+       da = fr_dict_root(proto_dict);
+       if (!da) {
+               fr_strerror_printf("Dictionary missing attribute root");
+               return NULL;
+       }
 
-       *tmp = NULL;
+       return da;
 }
 
-
-/** Build an unknown vendor, parented by a VSA or EVS attribute
- *
- * This allows us to complete the path back to the dictionary root in the case
- * of unknown attributes with unknown vendors.
- *
- * @note Will return known vendors attributes where possible.  Do not free directly,
- *     use #fr_dict_unknown_free.
- *
- * @param[in] ctx to allocate the vendor attribute in.
- * @param[out] out             Where to write point to new unknown dict attr
- *                             representing the unknown vendor.
- * @param[in] parent           of the vendor attribute, either an EVS or VSA attribute.
- * @param[in] vendor           id.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
+/*
+ *     Process the ATTRIBUTE command
  */
-int fr_dict_unknown_vendor_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
-                                    fr_dict_attr_t const *parent, unsigned int vendor)
+static int dict_read_process_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
+                                      unsigned int block_vendor, char **argv, int argc,
+                                      fr_dict_attr_flags_t *base_flags)
 {
-       fr_dict_attr_flags_t    flags = {
-                                       .is_unknown = true,
-                                       .is_raw = true,
-                                       .type_size = true,
-                                       .length = true
-                               };
+       bool                    oid = false;
 
-       if (!fr_cond_assert(parent)) {
-               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
+       unsigned int            vendor = 0;
+       unsigned int            attr;
+
+       int                     type;
+       unsigned int            length;
+       fr_dict_attr_flags_t    flags;
+       fr_dict_attr_t const    *ref = NULL;
+       char                    *p;
+
+       if ((argc < 3) || (argc > 4)) {
+               fr_strerror_printf("Invalid ATTRIBUTE syntax");
                return -1;
        }
 
-       *out = NULL;
-
        /*
-        *      Vendor attributes can occur under VSA or EVS attributes.
+        *      Dictionaries need to have real names, not shitty ones.
         */
-       switch (parent->type) {
-       case FR_TYPE_VSA:
-       case FR_TYPE_EVS:
-               if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
-
-               *out = dict_attr_alloc(ctx, parent, NULL, vendor, FR_TYPE_VENDOR, &flags);
-
-               return 0;
-
-       case FR_TYPE_VENDOR:
-               if (!fr_cond_assert(!parent->flags.is_unknown)) return -1;
-               fr_strerror_printf("Unknown vendor cannot be parented by another vendor");
-               return -1;
-
-       default:
-               fr_strerror_printf("Unknown vendors can only be parented by 'vsa' or 'evs' "
-                                  "attributes, not '%s'", fr_int2str(dict_attr_types, parent->type, "?Unknown?"));
+       if (strncmp(argv[0], "Attr-", 5) == 0) {
+               fr_strerror_printf("Invalid ATTRIBUTE name");
                return -1;
        }
-}
-
-/** Allocates an unknown attribute
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @note If vendor != 0, an unknown vendor (may) also be created, parented by
- *     the correct EVS or VSA attribute. This is accessible via da->parent,
- *     and will be use the unknown da as its talloc parent.
- *
- * @param[in] ctx              to allocate DA in.
- * @param[in] parent           of the unknown attribute (may also be unknown).
- * @param[in] attr             number.
- * @param[in] vendor           number.
- * @return 0 on success.
- */
-fr_dict_attr_t const *fr_dict_unknown_afrom_fields(TALLOC_CTX *ctx, fr_dict_attr_t const *parent,
-                                                  unsigned int vendor, unsigned int attr)
-{
-       fr_dict_attr_t const    *da;
-       fr_dict_attr_t          *n;
-       fr_dict_attr_t          *new_parent = NULL;
-       fr_dict_attr_flags_t    flags = {
-               .is_unknown     = true,
-               .is_raw         = true,
-       };
 
-       if (!fr_cond_assert(parent)) {
-               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
-               return NULL;
-       }
+       memcpy(&flags, base_flags, sizeof(flags));
 
        /*
-        *      If there's a vendor specified, we check to see
-        *      if the parent is a VSA or EVS, and if it is
-        *      we either lookup the vendor to get the correct
-        *      attribute, or bridge the gap in the tree, with an
-        *      unknown vendor.
-        *
-        *      We need to do the check, as the parent could be
-        *      a TLV, in which case the vendor should be known
-        *      and we don't need to modify the parent.
+        *      Look for OIDs before doing anything else.
         */
-       if (vendor && ((parent->type == FR_TYPE_VSA) || (parent->type == FR_TYPE_EVS))) {
-               da = fr_dict_attr_child_by_num(parent, vendor);
-               if (!da) {
-                       if (fr_dict_unknown_vendor_afrom_num(ctx, &new_parent, parent, vendor) < 0) return NULL;
-                       da = new_parent;
+       if (!strchr(argv[1], '.')) {
+               /*
+                *      Parse out the attribute number
+                */
+               if (!dict_read_sscanf_i(&attr, argv[1])) {
+                       fr_strerror_printf("Invalid ATTRIBUTE number");
+                       return -1;
                }
-               parent = da;
 
-       /*
-        *      Need to clone the unknown hierachy, as unknown
-        *      attributes must parent the complete heirachy,
-        *      and cannot share any parts with any other unknown
-        *      attributes.
-        */
-       } else if (parent->flags.is_unknown) {
-               new_parent = fr_dict_unknown_acopy(ctx, parent);
-               parent = new_parent;
-       }
+               /*
+                *      Got an OID string.  Every attribute should exist other
+                *      than the leaf, which is the attribute we're defining.
+                */
+       } else {
+               ssize_t slen;
 
-       n = dict_attr_alloc(ctx, parent, NULL, attr, FR_TYPE_OCTETS, &flags);
+               oid = true;
+
+               slen = fr_dict_attr_by_oid(dict, &parent, &attr, argv[1]);
+               if (slen <= 0) return -1;
+
+               if (!fr_cond_assert(parent)) return -1; /* Should have provided us with a parent */
+       }
 
        /*
-        *      The config files may reference the unknown by name.
-        *      If so, use the pre-defined name instead of an unknown
-        *      one.
-        *
-        *      @fixme: pass the root into this function!
+        *      Some types can have fixed length
         */
-       da = fr_dict_attr_by_name(NULL, n->name);
-       if (da) {
-               fr_dict_unknown_free(&parent);
-               parent = n;
-               fr_dict_unknown_free(&parent);
-               return da;
-       }
+       p = strchr(argv[2], '[');
+       if (p) *p = '\0';
 
        /*
-        *      Ensure the parent is freed at the same time as the
-        *      unknown DA.  This should be OK as we never parent
-        *      multiple unknown attributes off the same parent.
+        *      find the type of the attribute.
         */
-       if (new_parent && new_parent->flags.is_unknown) talloc_steal(n, new_parent);
+       type = fr_str2int(dict_attr_types, argv[2], -1);
+       if (type < 0) {
+               fr_strerror_printf("Unknown data type '%s'", argv[2]);
+               return -1;
+       }
 
-       return n;
-}
+       if (p) {
+               char *q;
 
-/** Initialise a fr_dict_attr_t from an ASCII attribute and value
- *
- * Where the attribute name is in the form:
- *  - Attr-%d
- *  - Attr-%d.%d.%d...
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @param[in] ctx              to allocate the attribute in.
- * @param[out] out             Where to write the new attribute to.
- * @param[in] parent           of the unknown attribute (may also be unknown).
- * @param[in] num              of the unknown attribute.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int dict_unknown_attr_afrom_num(TALLOC_CTX *ctx, fr_dict_attr_t **out,
-                                      fr_dict_attr_t const *parent, unsigned long num)
-{
-       fr_dict_attr_t          *da;
-       fr_dict_attr_flags_t    flags = {
-                                       .is_unknown = true,
-                                       .is_raw = true,
-                               };
+               q = strchr(p + 1, ']');
+               if (!q) {
+                       fr_strerror_printf("Invalid format for '%s[...]'", argv[2]);
+                       return -1;
+               }
 
-       if (!fr_cond_assert(parent)) {
-               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
-               return -1;
-       }
+               *q = '\0';
 
-       *out = NULL;
+               if (!dict_read_sscanf_i(&length, p + 1)) {
+                       fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
+                       return -1;
+               }
 
-       da = dict_attr_alloc(ctx, parent, NULL, num, FR_TYPE_OCTETS, &flags);
-       if (!da) return -1;
+               if ((length == 0) || (length > 253)) {
+                       fr_strerror_printf("Invalid length for '%s[...]'", argv[2]);
+                       return -1;
+               }
 
-       *out = da;
+               flags.length = length;
+       }
 
-       return 0;
-}
+       /*
+        *      Parse options.
+        */
+       if (argc >= 4) {
+               char *q, *v;
 
-/** Create a fr_dict_attr_t from an ASCII attribute and value
- *
- * Where the attribute name is in the form:
- *  - Attr-%d
- *  - Attr-%d.%d.%d...
- *
- * @copybrief fr_dict_unknown_from_fields
- *
- * @note If vendor != 0, an unknown vendor (may) also be created, parented by
- *     the correct EVS or VSA attribute. This is accessible via vp->parent,
- *     and will be use the unknown da as its talloc parent.
- *
- * @param[in] ctx              to alloc new attribute in.
- * @param[out] out             Where to write the head of the chain unknown
- *                             dictionary attributes.
- * @param[in] parent           Attribute to use as the root for resolving OIDs in.
- *                             Usually the root of a protocol dictionary.
- * @param[in] oid_str          of attribute.
- * @return
- *     - The number of bytes parsed on success.
- *     - <= 0 on failure.  Negative offset indicates parse error position.
- */
-ssize_t fr_dict_unknown_afrom_oid_str(TALLOC_CTX *ctx, fr_dict_attr_t **out,
-                                     fr_dict_attr_t const *parent, char const *oid_str)
-{
-       char const              *p = oid_str, *end = oid_str + strlen(oid_str);
-       fr_dict_attr_t const    *our_parent = parent;
-       TALLOC_CTX              *top_ctx = NULL, *our_ctx = ctx;
+               p = argv[3];
+               do {
+                       char key[64], value[256];
 
-       fr_dict_attr_t          *n = NULL;
+                       q = strchr(p, ',');
+                       if (!q) q = p + strlen(p);
 
-       if (!fr_cond_assert(parent)) {
-               fr_strerror_printf("%s: Invalid argument - parent was NULL", __FUNCTION__);
-               return -1;
-       }
+                       /*
+                        *      Nothing after the trailing comma
+                        */
+                       if (p == q) break;
 
-       *out = NULL;
+                       if ((size_t)(q - p) > sizeof(key)) {
+                               fr_strerror_printf("ATTRIBUTE option key too long");
+                               return -1;
+                       }
 
-       if (fr_dict_valid_name(oid_str, -1) < 0) return -1;
+                       /*
+                        *      Copy key and value
+                        */
+                       if (!(v = memchr(p, '=', q - p)) || (v == q)) {
+                               value[0] = '\0';
+                               strlcpy(key, p, (q - p) + 1);
+                       } else {
+                               strlcpy(key, p, (v - p) + 1);
+                               strlcpy(value, v + 1, q - v);
+                       }
 
-       /*
-        *      All unknown attributes are of the form "Attr-#-#-#-#"
-        */
-       if (strncasecmp(p, "Attr-", 5) != 0) {
-               fr_strerror_printf("Unknown attribute '%s'", oid_str);
-               return 0;
-       }
-       p += 5;
+                       /*
+                        *      Boolean flag, means this is a tagged
+                        *      attribute.
+                        */
+                       if (strcmp(key, "has_tag") == 0) {
+                               flags.has_tag = 1;
 
-       do {
-               unsigned int            num;
-               fr_dict_attr_t const    *da = NULL;
+                       /*
+                        *      Encryption method.
+                        */
+                       } else if (strcmp(key, "encrypt") == 0) {
+                               char *qq;
 
-               if (fr_dict_oid_component(&num, &p) < 0) {
-               error:
-                       talloc_free(top_ctx);
-                       return -(p - oid_str);
-               }
+                               flags.encrypt = strtol(value, &qq, 0);
+                               if (*qq) {
+                                       fr_strerror_printf("Invalid encrypt value \"%s\"", value);
+                                       return -1;
+                               }
 
-               switch (*p) {
-               /*
-                *      Structural attribute
-                */
-               case '.':
-                       if (!our_parent) goto is_root;
+                       /*
+                        *      Marks the attribute up as internal.
+                        *      This means it can use numbers outside of the allowed
+                        *      protocol range, and also means it will not be included
+                        *      in replies or proxy requests.
+                        */
+                       } else if (strcmp(key, "internal") == 0) {
+                               flags.internal = 1;
 
-                       da = fr_dict_attr_child_by_num(our_parent, num);
-                       if (!da) {      /* Unknown component */
-                               switch (our_parent->type) {
-                               case FR_TYPE_EVS:
-                               case FR_TYPE_VSA:
-                                       da = fr_dict_attr_child_by_num(our_parent, num);
-                                       if (!fr_cond_assert(!da || (da->type == FR_TYPE_VENDOR))) goto error;
+                       } else if (strcmp(key, "array") == 0) {
+                               flags.array = 1;
 
-                                       if (!da) {
-                                               if (fr_dict_unknown_vendor_afrom_num(our_ctx, &n,
-                                                                                    our_parent, num) < 0) {
-                                                       goto error;
-                                               }
-                                               da = n;
-                                       }
-                                       break;
+                       } else if (strcmp(key, "concat") == 0) {
+                               flags.concat = 1;
 
-                               case FR_TYPE_TLV:
-                               case FR_TYPE_EXTENDED:
-                               case FR_TYPE_LONG_EXTENDED:
-                               is_root:
-                                       if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) {
-                                               goto error;
-                                       }
+                       } else if (strcmp(key, "virtual") == 0) {
+                               flags.virtual = 1;
 
-                                       da = n;
-                                       break;
+                       } else if (strcmp(key, "reference") == 0) {
+                               ref = dict_resolve_reference(dict, value);
+                               if (!ref) return -1;
+                               flags.is_reference = 1;
 
-                               /*
-                                *      Can't have a FR_TYPE_STRING inside a
-                                *      FR_TYPE_STRING (for example)
-                                */
-                               default:
-                                       fr_strerror_printf("Parent OID component (%s) in \"%.*s\" specified a "
-                                                          "non-structural type (%s)", our_parent->name,
-                                                          (int)(p - oid_str), oid_str,
-                                                          fr_int2str(dict_attr_types, our_parent->type, "<INVALID>"));
-                                       goto error;
+                       /*
+                        *      The only thing is the vendor name, and it's a known name:
+                        *      allow it.
+                        */
+                       } else if ((argv[3] == p) && (*q == '\0')) {
+                               if (oid) {
+                                       fr_strerror_printf("ATTRIBUTE cannot use a 'vendor' flag");
+                                       return -1;
                                }
-                       }
-                       our_parent = da;
 
-                       if (n && n->flags.is_unknown) {
-                               if (top_ctx == NULL) top_ctx = n;       /* Track first unknown */
-                               our_ctx = n;
+                               if (block_vendor) {
+                                       fr_strerror_printf("Vendor flag inside of 'BEGIN-VENDOR' is not allowed");
+                                       return -1;
+                               }
+
+                               vendor = fr_dict_vendor_by_name(dict, key);
+                               if (!vendor) goto unknown;
+                               break;
+
+                       } else {
+                       unknown:
+                               fr_strerror_printf("Unknown option '%s'", key);
+                               return -1;
                        }
+                       p = q;
+               } while (*p++);
+       }
 
+#ifdef WITH_DICTIONARY_WARNINGS
+       /*
+        *      Hack to help us discover which vendors have illegal
+        *      attributes.
+        */
+       if (!vendor && (attr < 256) &&
+           !strstr(fn, "rfc") && !strstr(fn, "illegal")) {
+               fprintf(stderr, "WARNING: Illegal Attribute %s in %s\n",
+                       argv[0], fn);
+       }
+#endif
 
-                       break;
+       /*
+        *      Add in a normal attribute
+        */
+       if (!ref) {
+               if (fr_dict_attr_add(dict, parent, argv[0], attr, type, &flags) < 0) return -1;
+       /*
+        *      Add in a special reference attribute
+        */
+       } else {
+               if (dict_attr_ref_add(dict, parent, argv[0], attr, type, &flags, ref) < 0) return -1;
+       }
 
-               /*
-                *      Leaf attribute
-                */
-               case '\0':
-                       if (dict_unknown_attr_afrom_num(our_ctx, &n, our_parent, num) < 0) goto error;
-                       break;
-               }
-               p++;
-       } while (p < end);
+       return 0;
+}
 
-       if (!n) return 0;
+/*
+ *     Process the ATTRIBUTE command, where it only has a name.
+ */
+static int dict_read_process_named_attribute(fr_dict_t *dict, fr_dict_attr_t const *parent,
+                                            char **argv, int argc,
+                                            fr_dict_attr_flags_t const *base_flags)
+{
+       int type;
+       unsigned int attr;
+       uint32_t hash;
+       char *p, normalized[512];
+
+       if (argc != 2) {
+               fr_strerror_printf("Invalid ATTRIBUTE syntax");
+               return -1;
+       }
 
        /*
-        *      Invert the talloc hierarchy, so that if the unknown
-        *      attribute is freed, any unknown parents are also freed.
+        *      find the type of the attribute.
         */
-       for (our_parent = n->parent, our_ctx = n;
-            our_parent && our_parent->flags.is_unknown;
-            our_parent = our_parent->parent) {
-               fr_dict_attr_t *tmp;
-
-               memcpy(&tmp, &our_parent, sizeof(tmp));                 /* const issues *sigh* */
+       type = fr_str2int(dict_attr_types, argv[1], -1);
+       if (type < 0) {
+               fr_strerror_printf("Unknown data type '%s'", argv[1]);
+               return -1;
+       }
 
-               our_ctx = talloc_steal(our_ctx, tmp);
+       strlcpy(normalized, argv[0], sizeof(normalized));
+       for (p = normalized; *p != '\0'; p++) {
+               if (isupper((int) *p)) {
+                       *p = tolower((int) *p);
+               }
        }
 
-       DA_VERIFY(n);
+       hash = fr_hash_string(normalized);
+       attr = hash;
 
-       *out = n;
+       /*
+        *      Add it in.
+        */
+       if (fr_dict_attr_add(dict, parent, argv[0], attr, type, base_flags) < 0) return -1;
 
-       return end - oid_str;
+       return 0;
 }
 
-/** Create a dictionary attribute by name embedded in another string
- *
- * Find the first invalid attribute name char in the string pointed to by name.
- *
- * Copy the characters between the start of the name string and the first none
- * #fr_dict_attr_allowed_chars char to a buffer and initialise da as an unknown
- * attribute.
+/** Process a value alias
  *
- * @param[in] ctx              To allocate unknown #fr_dict_attr_t in.
- * @param[out] out             Where to write the head of the chain unknown
- *                             dictionary attributes.
- * @param[in] parent           Attribute to use as the root for resolving OIDs in.
- *                             Usually the root of a protocol dictionary.
- * @param[in] name             string start.
- * @return
- *     - <= 0 on failure.
- *     - The number of bytes of name consumed on success.
  */
-ssize_t fr_dict_unknown_afrom_oid_substr(TALLOC_CTX *ctx, fr_dict_attr_t **out,
-                                        fr_dict_attr_t const *parent, char const *name)
+static int dict_read_process_value(fr_dict_t *dict, char **argv, int argc)
 {
-       char const      *p;
-       size_t          len;
-       char            buffer[FR_DICT_ATTR_MAX_NAME_LEN + 1];
-       ssize_t         slen;
+       static fr_dict_attr_t const     *last_attr = NULL;
+       fr_dict_attr_t const            *da;
+       fr_value_box_t                  value;
 
-       if (!name || !*name) return 0;
+       if (argc != 3) {
+               fr_strerror_printf("Invalid VALUE syntax");
+               return -1;
+       }
 
        /*
-        *      Advance p until we get something that's not part of
-        *      the dictionary attribute name.
+        *      Most VALUEs are bunched together by ATTRIBUTE.  We can
+        *      save a lot of lookups on dictionary initialization by
+        *      caching the last attribute.
         */
-       for (p = name; fr_dict_attr_allowed_chars[(int)*p] || (*p == '.') || (*p == '-'); p++);
+       if (last_attr && (strcasecmp(argv[0], last_attr->name) == 0)) {
+               da = last_attr;
+       } else {
+               da = fr_dict_attr_by_name(dict, argv[0]);
+               last_attr = da;
+       }
+
+       /*
+        *      Remember which attribute is associated with this
+        *      value.  This allows us to define enum
+        *      values before the attribute exists, and fix them
+        *      up later.
+        */
+       if (!da) {
+               dict_enum_fixup_t *fixup;
+
+               fixup = talloc_zero(dict->pool, dict_enum_fixup_t);
+               if (!fixup) {
+               oom:
+                       talloc_free(fixup);
+                       fr_strerror_printf("Out of memory");
+                       return -1;
+               }
+               fixup->attribute = talloc_strdup(fixup, argv[0]);
+               if (!fixup->attribute) goto oom;
+               fixup->alias = talloc_strdup(fixup, argv[1]);
+               if (!fixup->alias) goto oom;
+               fixup->value = talloc_strdup(fixup, argv[2]);
+               if (!fixup->value) goto oom;
+
+               /*
+                *      Insert to the head of the list.
+                */
+               fixup->next = dict->enum_fixup;
+               dict->enum_fixup = fixup;
 
-       len = p - name;
-       if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
-               fr_strerror_printf("Attribute name too long");
                return 0;
        }
-       if (len == 0) {
-               fr_strerror_printf("Invalid attribute name");
-               return 0;
+
+       {
+               fr_type_t type = da->type;      /* Might change - Stupid combo IP */
+
+               if (fr_value_box_from_str(NULL, &value, &type, NULL, argv[2], -1, '\0', false) < 0) {
+                       fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+                       return -1;
+               }
        }
-       strlcpy(buffer, name, len + 1);
 
-       slen = fr_dict_unknown_afrom_oid_str(ctx, out, parent, buffer);
-       if (slen <= 0) return slen;
+       if (fr_dict_enum_add_alias(da, argv[1], &value, false, true) < 0) {
+               fr_value_box_clear(&value);
+               return -1;
+       }
+       fr_value_box_clear(&value);
 
-       return p - name;
+       return 0;
 }
 
-
-/** Check to see if we can convert a nested TLV structure to known attributes
- *
- * @param[in] dict                     to search in.
- * @param[in] da                       Nested tlv structure to convert.
- * @return
- *     - NULL if we can't.
- *     - Known attribute if we can.
+/*
+ *     Process the FLAGS command
  */
-fr_dict_attr_t const *fr_dict_attr_known(fr_dict_t *dict, fr_dict_attr_t const *da)
+static int dict_read_process_flags(UNUSED fr_dict_t *dict, char **argv, int argc,
+                                  fr_dict_attr_flags_t *base_flags)
 {
-       INTERNAL_IF_NULL(dict);
+       bool sense = true;
 
-       if (!da->flags.is_unknown) return da;   /* It's known */
+       if (argc == 1) {
+               char *p;
 
-       if (da->parent) {
-               fr_dict_attr_t const *parent;
+               p = argv[0];
+               if (*p == '!') {
+                       sense = false;
+                       p++;
+               }
 
-               parent = fr_dict_attr_known(dict, da->parent);
-               if (!parent) return NULL;
+               if (strcmp(p, "internal") == 0) {
+                       base_flags->internal = sense;
+                       return 0;
+               }
+       }
 
-               return fr_dict_attr_child_by_num(parent, da->attr);
+       fr_strerror_printf("Invalid FLAGS syntax");
+       return -1;
+}
+
+static int dict_read_parse_format(char const *format, unsigned int *pvalue, int *ptype, int *plength,
+                                 bool *pcontinuation)
+{
+       char const *p;
+       int type, length;
+       bool continuation = false;
+
+       if (strncasecmp(format, "format=", 7) != 0) {
+               fr_strerror_printf("Invalid format for VENDOR.  Expected 'format=', got '%s'",
+                                  format);
+               return -1;
        }
 
-       if (dict->root == da) return dict->root;
-       return NULL;
+       p = format + 7;
+       if ((strlen(p) < 3) ||
+           !isdigit((int)p[0]) ||
+           (p[1] != ',') ||
+           !isdigit((int)p[2]) ||
+           (p[3] && (p[3] != ','))) {
+               fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
+                                  p);
+               return -1;
+       }
+
+       type = (int)(p[0] - '0');
+       length = (int)(p[2] - '0');
+
+       if ((type != 1) && (type != 2) && (type != 4)) {
+               fr_strerror_printf("Invalid type value %d for VENDOR", type);
+               return -1;
+       }
+
+       if ((length != 0) && (length != 1) && (length != 2)) {
+               fr_strerror_printf("Ivalid length value %d for VENDOR", length);
+               return -1;
+       }
+
+       if (p[3] == ',') {
+               if (!p[4]) {
+                       fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
+                                          p);
+                       return -1;
+               }
+
+               if ((p[4] != 'c') ||
+                   (p[5] != '\0')) {
+                       fr_strerror_printf("Invalid format for VENDOR.  Expected text like '1,1', got '%s'",
+                                          p);
+                       return -1;
+               }
+               continuation = true;
+
+               if ((*pvalue != VENDORPEC_WIMAX) ||
+                   (type != 1) || (length != 1)) {
+                       fr_strerror_printf("Only WiMAX VSAs can have continuations");
+                       return -1;
+               }
+       }
+
+       *ptype = type;
+       *plength = length;
+       *pcontinuation = continuation;
+       return 0;
 }
 
-static void dict_snprint_flags(char *out, size_t outlen, fr_dict_attr_flags_t flags)
+/** Register the specified dictionary as a protocol dictionary
+ *
+ * Allows vendor and TLV context to persist across $INCLUDEs
+ */
+static int dict_read_process_protocol(char **argv, int argc)
 {
-       char *p = out, *end = p + outlen;
-       size_t len;
+       unsigned int    value;
+       unsigned int    type_size = 1;
+       fr_dict_t       *dict;
 
-       out[0] = '\0';
+       if ((argc < 2) || (argc > 3)) {
+               fr_strerror_printf("Missing arguments after PROTOCOL.  Expected PROTOCOL <num> <name>");
+               return -1;
+       }
 
-#define FLAG_SET(_flag) \
-do { \
-       if (flags._flag) {\
-               p += strlcpy(p, STRINGIFY(_flag)",", end - p);\
-               if (p >= end) return;\
-       }\
-} while (0)
+       /*
+        *       Validate all entries
+        */
+       if (!dict_read_sscanf_i(&value, argv[1])) {
+               fr_strerror_printf("Invalid number '%s' following PROTOCOL", argv[1]);
+               return -1;
+       }
 
-       FLAG_SET(is_root);
-       FLAG_SET(is_unknown);
-       FLAG_SET(is_raw);
-       FLAG_SET(internal);
-       FLAG_SET(has_tag);
-       FLAG_SET(array);
-       FLAG_SET(has_value);
-       FLAG_SET(concat);
-       FLAG_SET(virtual);
-       FLAG_SET(compare);
+       /*
+        *      Look for a format statement.  This may specify the
+        *      type length of the protocol's types.
+        */
+       if (argc == 3) {
+               char const *p;
+               char *q;
 
-       if (flags.encrypt) {
-               p += snprintf(p, end - p, "encrypt=%i,", flags.encrypt);
-               if (p >= end) return;
+               if (strncasecmp(argv[2], "format=", 7) != 0) {
+                       fr_strerror_printf("Invalid format for PROTOCOL.  Expected 'format=', got '%s'", argv[2]);
+                       return -1;
+               }
+               p = argv[2] + 7;
+
+               type_size = strtoul(p, &q, 10);
+               if (q != (p + strlen(p))) {
+                       fr_strerror_printf("Found trailing garbage '%s' after format specifier", p);
+                       return -1;
+               }
        }
 
-       if (flags.length) {
-               p += snprintf(p, end - p, "length=%i,", flags.length);
-               if (p >= end) return;
+       dict = fr_dict_by_protocol_num(value);
+       if (dict) {
+               if (dict->root->flags.type_size != type_size) {
+                       fr_strerror_printf("Conflicting flags for PROTOCOL \"%s\"", dict->root->name);
+                       return -1;
+               }
+               return 0;
        }
 
-       if (!out[0]) return;
+       dict = dict_alloc(NULL);
 
        /*
-        *      Trim the comma
+        *      Set the root attribute with the protocol name
         */
-       len = strlen(out);
-       if (out[len - 1] == ',') out[len - 1] = '\0';
+       dict_root_set(dict, argv[0], value);
+
+       if (dict_protocol_add(dict) < 0) return -1;
+
+       return 0;
 }
 
-void fr_dict_print(fr_dict_attr_t const *da, int depth)
+/*
+ *     Process the VENDOR command
+ */
+static int dict_read_process_vendor(fr_dict_t *dict, char **argv, int argc)
 {
-       char buff[256];
-       unsigned int i;
-       char const *name;
+       unsigned int                    value;
+       int                             type, length;
+       bool                            continuation = false;
+       fr_dict_vendor_t const          *dv;
+       fr_dict_vendor_t                *mutable;
 
-       dict_snprint_flags(buff, sizeof(buff), da->flags);
+       if ((argc < 2) || (argc > 3)) {
+               fr_strerror_printf("Invalid VENDOR syntax");
+               return -1;
+       }
 
-       switch (da->type) {
-       case FR_TYPE_VSA:
-               name = "VSA";
-               break;
+       /*
+        *       Validate all entries
+        */
+       if (!dict_read_sscanf_i(&value, argv[1])) {
+               fr_strerror_printf("Invalid number in VENDOR");
+               return -1;
+       }
 
-       case FR_TYPE_EXTENDED:
-               name = "EXTENDED";
-               break;
+       /* Create a new VENDOR entry for the list */
+       if (dict_vendor_add(dict, argv[0], value) < 0) return -1;
 
-       case FR_TYPE_TLV:
-               name = "TLV";
-               break;
+       /*
+        *      Look for a format statement.  Allow it to over-ride the hard-coded formats below.
+        */
+       if (argc == 3) {
+               if (dict_read_parse_format(argv[2], &value, &type, &length, &continuation) < 0) return -1;
 
-       case FR_TYPE_EVS:
-               name = "EVS";
-               break;
+       } else if (value == VENDORPEC_USR) { /* catch dictionary screw-ups */
+               type = 4;
+               length = 0;
 
-       case FR_TYPE_VENDOR:
-               name = "VENDOR";
-               break;
+       } else if (value == VENDORPEC_LUCENT) {
+               type = 2;
+               length = 1;
 
-       case FR_TYPE_LONG_EXTENDED:
-               name = "LONG EXTENDED";
-               break;
+       } else if (value == VENDORPEC_STARENT) {
+               type = 2;
+               length = 2;
 
-       case FR_TYPE_STRUCT:
-               name = "STRUCT";
-               break;
+       } else {
+               type = length = 1;
+       }
 
-       default:
-               name = "ATTRIBUTE";
-               break;
+       dv = fr_dict_vendor_by_num(dict, value);
+       if (!dv) {
+               fr_strerror_printf("Failed adding format for VENDOR");
+               return -1;
        }
 
-       printf("%u%.*s%s \"%s\" vendor: %x (%u), num: %x (%u), type: %s, flags: %s\n", da->depth, depth,
-              "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t", name, da->name,
-              fr_dict_vendor_num_by_da(da), fr_dict_vendor_num_by_da(da), da->attr, da->attr,
-              fr_int2str(dict_attr_types, da->type, "?Unknown?"), buff);
+       memcpy(&mutable, &dv, sizeof(mutable));
 
-       if (da->children) for (i = 0; i < talloc_array_length(da->children); i++) {
-               if (da->children[i]) {
-                       fr_dict_attr_t const *bin;
+       mutable->type = type;
+       mutable->length = length;
+       mutable->flags = continuation;
 
-                       for (bin = da->children[i]; bin; bin = bin->next) fr_dict_print(bin, depth + 1);
-               }
-       }
+       return 0;
 }
 
-/** Find a common ancestor that two TLV type attributes share
+/** Parse a dictionary file
  *
- * @param[in] a                        first TLV attribute.
- * @param[in] b                        second TLV attribute.
- * @param[in] is_ancestor      Enforce a->b relationship (a is parent or ancestor of b).
+ * @param[in] ctx      Contains the current state of the dictionary parser.
+ *                     Used to track what PROTOCOL, VENDOR or TLV block
+ *                     we're in. Block context changes in $INCLUDEs should
+ *                     not affect the context of the including file.
+ * @param[in] dir_name Directory containing the dictionary we're loading.
+ * @param[in] filename we're parsing.
+ * @param[in] src_file The including file.
+ * @param[in] src_line Line on which the $INCLUDE or $INCLUDE- statement was found.
  * @return
- *     - Common ancestor if one exists.
- *     - NULL if no common ancestor exists.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-fr_dict_attr_t const *fr_dict_parent_common(fr_dict_attr_t const *a, fr_dict_attr_t const *b, bool is_ancestor)
+static int _dict_from_file(dict_from_file_ctx_t *ctx,
+                          char const *dir_name, char const *filename,
+                          char const *src_file, int src_line)
 {
-       unsigned int i;
-       fr_dict_attr_t const *p_a, *p_b;
-
-       if (!a || !b) return NULL;
+       FILE                    *fp;
+       char                    dir[256], fn[256];
+       char                    buf[256];
+       char                    *p;
+       int                     line = 0;
 
-       if (is_ancestor && (b->depth <= a->depth)) return NULL;
+       struct stat             statbuf;
+       char                    *argv[MAX_ARGV];
+       int                     argc;
+       fr_dict_attr_t const    *da;
 
        /*
-        *      Find a common depth to work back from
+        *      Base flags are only set for the current file
         */
-       if (a->depth > b->depth) {
-               p_b = b;
-               for (p_a = a, i = a->depth - b->depth; p_a && (i > 0); p_a = p_a->parent, i--);
-       } else if (a->depth < b->depth) {
-               p_a = a;
-               for (p_b = b, i = b->depth - a->depth; p_b && (i > 0); p_b = p_b->parent, i--);
-       } else {
-               p_a = a;
-               p_b = b;
-       }
+       fr_dict_attr_flags_t    base_flags;
 
-       while (p_a && p_b) {
-               if (p_a == p_b) return p_a;
+       if (!fr_cond_assert(!ctx->dict->root || ctx->parent)) return -1;
 
-               p_a = p_a->parent;
-               p_b = p_b->parent;
+       if ((strlen(dir_name) + 3 + strlen(filename)) > sizeof(dir)) {
+               fr_strerror_printf_push("%s: Filename name too long", "Error reading dictionary");
+               return -1;
        }
 
-       return NULL;
-}
-
-/** Process a single OID component
- *
- * @param[out] out             Value of component.
- * @param[in] oid              string to parse.
- * @return
- *     - 0 on success.
- *     - -1 on format error.
- */
-int fr_dict_oid_component(unsigned int *out, char const **oid)
-{
-       char const *p = *oid;
-       char *q;
-       unsigned long num;
+       /*
+        *      If it's an absolute dir, forget the parent dir,
+        *      and remember the new one.
+        *
+        *      If it's a relative dir, tack on the current filename
+        *      to the parent dir.  And use that.
+        */
+       if (!FR_DIR_IS_RELATIVE(filename)) {
+               strlcpy(dir, filename, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       p[1] = '\0';
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
 
-       *out = 0;
+               strlcpy(fn, filename, sizeof(fn));
+       } else {
+               strlcpy(dir, dir_name, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       if (p[1]) strlcat(dir, "/", sizeof(dir));
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
+               strlcat(dir, filename, sizeof(dir));
+               p = strrchr(dir, FR_DIR_SEP);
+               if (p) {
+                       p[1] = '\0';
+               } else {
+                       strlcat(dir, "/", sizeof(dir));
+               }
 
-       num = strtoul(p, &q, 10);
-       if ((p == q) || (num == ULONG_MAX)) {
-               fr_strerror_printf("Invalid OID component \"%s\" (%lu)", p, num);
-               return -1;
+               p = strrchr(filename, FR_DIR_SEP);
+               if (p) {
+                       snprintf(fn, sizeof(fn), "%s%s", dir, p);
+               } else {
+                       snprintf(fn, sizeof(fn), "%s%s", dir, filename);
+               }
        }
 
-       switch (*q) {
-       case '\0':
-       case '.':
-               *oid = q;
-               *out = (unsigned int)num;
+       /*
+        *      Check if we've loaded this file before.  If so, ignore it.
+        */
+       p = strrchr(fn, FR_DIR_SEP);
+       if (p) {
+               *p = '\0';
+               if (dict_stat_check(ctx->dict, fn, p + 1)) {
+                       *p = FR_DIR_SEP;
+                       return 0;
+               }
+               *p = FR_DIR_SEP;
+       }
 
-               return 0;
+       if ((fp = fopen(fn, "r")) == NULL) {
+               if (!src_file) {
+                       fr_strerror_printf_push("%s: Couldn't open dictionary '%s': %s",
+                                          "Error reading dictionary", fn, fr_syserror(errno));
+               } else {
+                       fr_strerror_printf_push("%s: %s[%d]: Couldn't open dictionary '%s': %s",
+                                          "Error reading dictionary", src_file, src_line, fn, fr_syserror(errno));
+               }
+               return -2;
+       }
 
-       default:
-               fr_strerror_printf("Unexpected text after OID component");
-               *out = 0;
+       /*
+        *      If fopen works, this works.
+        */
+       if (stat(fn, &statbuf) < 0) {
+               fclose(fp);
                return -1;
        }
-}
-
-/** Build the tlv_stack for the specified DA and encode the path in OID form
- *
- * @param[out] out             Where to write the OID.
- * @param[in] outlen           Length of the output buffer.
- * @param[in] ancestor         If not NULL, only print OID portion between
- *                             ancestor and da.
- * @param[in] da               to print OID string for.
- * @return the number of bytes written to the buffer.
- */
-size_t fr_dict_print_attr_oid(char *out, size_t outlen,
-                             fr_dict_attr_t const *ancestor, fr_dict_attr_t const *da)
-{
-       size_t                  len;
-       char                    *p = out, *end = p + outlen;
-       int                     i;
-       int                     depth = 0;
-       fr_dict_attr_t const    *tlv_stack[FR_DICT_MAX_TLV_STACK + 1];
 
-       if (!outlen) return 0;
+       if (!S_ISREG(statbuf.st_mode)) {
+               fclose(fp);
+               fr_strerror_printf_push("%s: Dictionary '%s' is not a regular file", "Error reading dictionary", fn);
+               return -1;
+       }
 
        /*
-        *      If the ancestor and the DA match, there's
-        *      no OID string to print.
+        *      Globally writable dictionaries means that users can control
+        *      the server configuration with little difficulty.
         */
-       if (ancestor == da) {
-               out[0] = '\0';
-               return 0;
+#ifdef S_IWOTH
+       if ((statbuf.st_mode & S_IWOTH) != 0) {
+               fclose(fp);
+               fr_strerror_printf_push("%s: Dictionary '%s' is globally writable.  Refusing to start "
+                                  "due to insecure configuration", "Error reading dictionary", fn);
+               return -1;
        }
+#endif
 
-       fr_proto_tlv_stack_build(tlv_stack, da);
-
-       if (ancestor) {
-               if (tlv_stack[ancestor->depth - 1] != ancestor) {
-                       fr_strerror_printf("Attribute \"%s\" is not a descendent of \"%s\"", da->name, ancestor->name);
-                       return -1;
-               }
-               depth = ancestor->depth;
-       }
+       dict_stat_add(ctx->dict, &statbuf);
 
        /*
-        *      We don't print the ancestor, we print the OID
-        *      between it and the da.
+        *      Seed the random pool with data.
         */
-       len = snprintf(p, end - p, "%u", tlv_stack[depth]->attr);
-       if ((p + len) >= end) return p - out;
-       p += len;
+       fr_rand_seed(&statbuf, sizeof(statbuf));
 
+       memset(&base_flags, 0, sizeof(base_flags));
 
-       for (i = depth + 1; i < (int)da->depth; i++) {
-               len = snprintf(p, end - p, ".%u", tlv_stack[i]->attr);
-               if ((p + len) >= end) return p - out;
-               p += len;
-       }
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               line++;
 
-       return p - out;
-}
+               switch (buf[0]) {
+               case '#':
+               case '\0':
+               case '\n':
+               case '\r':
+                       continue;
+               }
 
-/** Get the leaf attribute of an OID string
- *
- * @note On error, vendor will be set (if present), parent will be the
- *     maximum depth we managed to resolve to, and attr will be the child
- *     we failed to resolve.
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[out] attr            Number we parsed.
- * @param[in,out] parent       attribute (or root of dictionary).
- *                             Will be updated to the parent directly beneath the leaf.
- * @param[in] oid              string to parse.
- * @return
- *     - > 0 on success (number of bytes parsed).
- *     - <= 0 on parse error (negative offset of parse error).
- */
-ssize_t fr_dict_attr_by_oid(fr_dict_t *dict, fr_dict_attr_t const **parent, unsigned int *attr, char const *oid)
-{
-       char const              *p = oid;
-       unsigned int            num = 0;
-       ssize_t                 slen;
+               /*
+                *  Comment characters should NOT be appearing anywhere but
+                *  as start of a comment;
+                */
+               p = strchr(buf, '#');
+               if (p) *p = '\0';
 
-       if (!fr_cond_assert(parent)) return 0;
-       INTERNAL_IF_NULL(dict);
+               argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
+               if (argc == 0) continue;
 
-       *attr = 0;
+               if (argc == 1) {
+                       fr_strerror_printf("Invalid entry");
 
-       if (fr_dict_oid_component(&num, &p) < 0) return oid - p;
+               error:
+                       fr_strerror_printf_push("Error reading %s[%d]", fn, line);
+                       fclose(fp);
+                       return -1;
+               }
 
-       /*
-        *      Record progress even if we error out.
-        *
-        *      Don't change this, you will break things.
-        */
-       *attr = num;
+               /*
+                *      Process VALUE lines.
+                */
+               if (strcasecmp(argv[0], "VALUE") == 0) {
+                       if (dict_read_process_value(ctx->dict, argv + 1, argc - 1) == -1) goto error;
+                       continue;
+               }
 
-       switch ((*parent)->type) {
-       case FR_TYPE_STRUCTURAL:
-               break;
+               /*
+                *      Perhaps this is an attribute.
+                */
+               if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
+                       if (!base_flags.named) {
+                               if (dict_read_process_attribute(ctx->dict, ctx->parent, ctx->block_vendor,
+                                                               argv + 1, argc - 1,
+                                                               &base_flags) == -1) goto error;
+                       } else {
+                               if (dict_read_process_named_attribute(ctx->dict, ctx->parent,
+                                                                     argv + 1, argc - 1,
+                                                                     &base_flags) == -1) goto error;
+                       }
+                       continue;
+               }
 
-       default:
-               fr_strerror_printf("Attribute %s (%i) is not a TLV, so cannot contain a child attribute.  "
-                                  "Error at sub OID \"%s\"", (*parent)->name, (*parent)->attr, oid);
-               return 0;       /* We parsed nothing */
-       }
+               /*
+                *      Process VALUE lines.
+                */
+               if (strcasecmp(argv[0], "FLAGS") == 0) {
+                       if (dict_read_process_flags(ctx->dict, argv + 1, argc - 1, &base_flags) == -1) goto error;
+                       continue;
+               }
 
-       /*
-        *      If it's not a vendor type, it must be between 0..8*type_size
-        *
-        *      @fixme: find the TLV parent, and check it's size
-        */
-       if (((*parent)->type != FR_TYPE_VENDOR) && ((*parent)->type != FR_TYPE_VSA) && !(*parent)->flags.is_root &&
-           (num > UINT8_MAX)) {
-               fr_strerror_printf("TLV attributes must be between 0..255 inclusive");
-               return 0;
-       }
+               /*
+                *      See if we need to import another dictionary.
+                */
+               if (strcasecmp(argv[0], "$INCLUDE") == 0) {
+                       dict_from_file_ctx_t nctx = *ctx;
 
-       switch (p[0]) {
-       /*
-        *      We've not hit the leaf yet, so the attribute must be
-        *      defined already.
-        */
-       case '.':
-       {
-               fr_dict_attr_t const *child;
-               p++;
+                       /*
+                        *      Included files operate on a copy of the context
+                        */
+                       if (_dict_from_file(&nctx, dir, argv[1], fn, line) < 0) {
+                               fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
+                               fclose(fp);
+                               return -1;
+                       }
+                       continue;
+               } /* $INCLUDE */
 
-               child = fr_dict_attr_child_by_num(*parent, num);
-               if (!child) {
-                       fr_strerror_printf("Unknown attribute \"%i\" in OID string \"%s\"", num, oid);
-                       return 0;       /* We parsed nothing */
+               /*
+                *      Optionally include a dictionary
+                */
+               if (strcasecmp(argv[0], "$INCLUDE-") == 0) {
+                       int rcode = _dict_from_file(ctx, dir, argv[1], fn, line);
+
+                       if (rcode == -2) {
+                               fr_strerror_printf(NULL); /* delete all errors */
+                               continue;
+                       }
+
+                       if (rcode < 0) {
+                               fr_strerror_printf_push("from $INCLUDE at %s[%d]", fn, line);
+                               fclose(fp);
+                               return -1;
+                       }
+                       continue;
+               } /* $INCLUDE- */
+
+               /*
+                *      Process VENDOR lines.
+                */
+               if (strcasecmp(argv[0], "VENDOR") == 0) {
+                       if (dict_read_process_vendor(ctx->dict, argv + 1, argc - 1) == -1) goto error;
+                       continue;
                }
 
                /*
-                *      Record progress even if we error out.
-                *
-                *      Don't change this, you will break things.
+                *      Process PROTOCOL line.  Defines a new protocol.
                 */
-               *parent = child;
-
-               slen = fr_dict_attr_by_oid(dict, parent, attr, p);
-               if (slen <= 0) return slen - (p - oid);
-               return slen + (p - oid);
-       }
-
-       /*
-        *      Hit the leaf, this is the attribute we need to define.
-        */
-       case '\0':
-               *attr = num;
-               return p - oid;
-
-       default:
-               fr_strerror_printf("Malformed OID string, got trailing garbage '%s'", p);
-               return oid - p;
-       }
-}
-
-/** Lookup a protocol by its name
- *
- * @param[in] name of the protocol to locate.
- * @return
- *     - Attribute matching name.
- *     - NULL if no matching protocolibute could be found.
- */
-fr_dict_t *fr_dict_by_protocol_name(char const *name)
-{
-       fr_dict_attr_t  root = { .name = name };
-       fr_dict_t       find = { .root = &root };
-
-       if (!protocol_by_name || !name) return NULL;
+               if (strcasecmp(argv[0], "PROTOCOL") == 0) {
+                       if (argc < 2) {
+                               fr_strerror_printf("Invalid PROTOCOL entry");
+                               goto error;
+                       }
+                       if (dict_read_process_protocol(argv + 1, argc - 1) == -1) goto error;
+                       continue;
+               }
 
-       return fr_hash_table_finddata(protocol_by_name, &find);
-}
+               /*
+                *      Switches the current protocol context
+                */
+               if (strcasecmp(argv[0], "BEGIN-PROTOCOL") == 0) {
+                       fr_dict_t *found;
 
-/** Lookup a protocol by its number.
- *
- * Returns the #fr_dict_t belonging to the protocol with the specified number
- * if any have been registered.
- *
- * @param[in] num to search for.
- * @return dictionary representing the protocol (if it exists).
- */
-fr_dict_t *fr_dict_by_protocol_num(unsigned int num)
-{
-       fr_dict_t       find;
-       fr_dict_attr_t  root;
+                       ctx->old_dict = ctx->dict;
 
-       if (!protocol_by_num) return NULL;
+                       if (argc != 2) {
+                               fr_strerror_printf("Invalid BEGIN-PROTOCOL entry");
+                               goto error;
+                       }
 
-       memset(&find, 0, sizeof(find));
-       memset(&root, 0, sizeof(root));
+                       found = fr_dict_by_protocol_name(argv[1]);
+                       if (!found) {
+                               fr_strerror_printf("Unknown protocol '%s'", argv[1]);
+                               goto error;
+                       }
 
-       find.root = &root;
-       root.attr = num;
+                       ctx->dict = found;
 
-       return fr_hash_table_finddata(protocol_by_num, &find);
-}
+                       continue;
+               }
 
-/** Dictionary/attribute ctx struct
- *
- */
-typedef struct {
-       fr_dict_t               *found_dict;    //!< Dictionary attribute found in.
-       fr_dict_attr_t const    *found_da;      //!< Resolved attribute.
-       fr_dict_attr_t const    *find;          //!< Attribute to find.
-} dict_attr_search_t;
+               /*
+                *      Switches back to the previous protocol context
+                */
+               if (strcasecmp(argv[0], "END-PROTOCOL") == 0) {
+                       fr_dict_t const *found;
 
-/** Search for an attribute name in all dictionaries
- *
- * @param[in] ctx      Attribute to search for.
- * @param[in] data     Dictionary to search in.
- * @return
- *     - 0 if attribute not found in dictionary.
- *     - 1 if attribute found in dictionary.
- */
-static int _dict_attr_find_in_dicts(void *ctx, void *data)
-{
-       dict_attr_search_t      *search = ctx;
-       fr_dict_t               *dict;
+                       if (argc != 2) {
+                               fr_strerror_printf("Invalid END-PROTOCOL entry");
+                               goto error;
+                       }
 
-       if (!data) return 0;    /* We get called with NULL data */
+                       found = fr_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;
+                       }
 
-       dict = talloc_get_type_abort(data, fr_dict_t);
+                       if (found != ctx->dict) {
+                               fr_strerror_printf("END-PROTOCOL %s does not match previous BEGIN-PROTOCOL %s",
+                                                  argv[1], found->root->name);
+                               goto error;
+                       }
 
-       search->found_da = fr_hash_table_finddata(dict->attributes_by_name, search->find);
-       if (!search->found_da) return 0;
+                       ctx->dict = ctx->old_dict;      /* Switch back to the old dictionary */
 
-       search->found_dict = data;
+                       continue;
+               }
 
-       return 1;
-}
+               /*
+                *      Switches TLV parent context
+                */
+               if (strcasecmp(argv[0], "BEGIN-TLV") == 0) {
+                       fr_dict_attr_t const *common;
 
-/** Attempt to locate the protocol dictionary containing an attribute
- *
- * @note Unlike fr_dict_by_attr_name, doesn't search through all the dictionaries,
- *     just uses the fr_dict_attr_t hierarchy and the talloc hierarchy to locate
- *     the dictionary (much much faster and more scalable).
- *
- * @param[in] da               To get the containing dictionary for.
- * @return
- *     - The dictionary containing da.
- *     - NULL.
- */
-fr_dict_t *fr_dict_by_da(fr_dict_attr_t const *da)
-{
-       fr_dict_attr_t const *da_p = da;
+                       if ((ctx->block_tlv_depth + 1) > FR_DICT_TLV_NEST_MAX) {
+                               fr_strerror_printf_push("TLVs are nested too deep");
+                               goto error;
+                       }
 
-       while (da_p->parent) {
-               da_p = da_p->parent;
-               DA_VERIFY(da_p);
-       }
+                       if (argc != 2) {
+                               fr_strerror_printf_push("Invalid BEGIN-TLV entry");
+                               goto error;
+                       }
 
-       if (!da_p->flags.is_root) {
-               fr_strerror_printf("%s: Attribute %s has not been inserted into a dictionary", __FUNCTION__, da->name);
-               return NULL;
-       }
+                       da = fr_dict_attr_by_name(ctx->dict, argv[1]);
+                       if (!da) {
+                               fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
+                               goto error;
+                       }
 
-       /*
-        *      Parent of the root attribute must
-        *      be the dictionary.
-        */
-       return talloc_get_type_abort(talloc_parent(da_p), fr_dict_t);
-}
+                       if (da->type != FR_TYPE_TLV) {
+                               fr_strerror_printf_push("Attribute '%s' should be a 'tlv', but is a '%s'",
+                                                       argv[1],
+                                                       fr_int2str(dict_attr_types, da->type, "?Unknown?"));
+                               goto error;
+                       }
 
-/** Attempt to locate the protocol dictionary containing an attribute
- *
- * @note This is O(n) and will only return the first instance of the dictionary.
- *
- * @param[out] found   the attribute that was resolved from the name.
- * @param[in] name     the name of the attribute.
- * @return
- *     - the dictionary the attribute was found in.
- *     - NULL if an attribute with the specified name wasn't found in any dictionary.
- */
-fr_dict_t *fr_dict_by_attr_name(fr_dict_attr_t const **found, char const *name)
-{
-       fr_dict_attr_t          find = {
-                                       .name = name
-                               };
-       dict_attr_search_t      search = {
-                                       .find = &find
-                               };
-       int                     ret;
+                       common = fr_dict_parent_common(ctx->parent, da, true);
+                       if (!common ||
+                           (common->type == FR_TYPE_VSA) ||
+                           (common->type == FR_TYPE_EVS)) {
+                               fr_strerror_printf_push("Attribute '%s' should be a child of '%s'",
+                                                       argv[1], ctx->parent->name);
+                               goto error;
+                       }
 
-       *found = NULL;
+                       ctx->block_tlv[ctx->block_tlv_depth++] = ctx->parent;
+                       ctx->parent = da;
 
-       if (!name || !*name) return NULL;
+                       continue;
+               } /* BEGIN-TLV */
 
-       ret = fr_hash_table_walk(protocol_by_name, _dict_attr_find_in_dicts, &search);
-       if (ret == 0) return NULL;
+               /*
+                *      Switches back to previous TLV parent
+                */
+               if (strcasecmp(argv[0], "END-TLV") == 0) {
+                       if (--ctx->block_tlv_depth < 0) {
+                               fr_strerror_printf_push("Too many END-TLV entries.  Mismatch at END-TLV %s", argv[1]);
+                               goto error;
+                       }
 
-       if (found) *found = search.found_da;
+                       if (argc != 2) {
+                               fr_strerror_printf_push("Invalid END-TLV entry");
+                               goto error;
+                       }
 
-       return search.found_dict;
-}
+                       da = fr_dict_attr_by_name(ctx->dict, argv[1]);
+                       if (!da) {
+                               fr_strerror_printf_push("Unknown attribute '%s'", argv[1]);
+                               goto error;
+                       }
 
-/** Look up a vendor by its name
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] name             to search for.
- * @return
- *     - The vendor.
- *     - NULL if no vendor with that name was regitered for this protocol.
- */
-int fr_dict_vendor_by_name(fr_dict_t const *dict, char const *name)
-{
-       fr_dict_vendor_t find = { .name = name }, *found;
+                       if (da != ctx->parent) {
+                               fr_strerror_printf_push("END-TLV %s does not match previous BEGIN-TLV %s", argv[1],
+                                                  ctx->parent->name);
+                               goto error;
+                       }
+                       ctx->parent = ctx->block_tlv[ctx->block_tlv_depth];
+                       continue;
+               } /* END-VENDOR */
 
-       if (!name) return 0;
-       INTERNAL_IF_NULL(dict);
+               if (strcasecmp(argv[0], "BEGIN-VENDOR") == 0) {
+                       unsigned int            vendor;
+                       fr_dict_attr_flags_t    flags;
 
-       found = fr_hash_table_finddata(dict->vendors_by_name, &find);
-       if (!found) return 0;
+                       fr_dict_attr_t const    *vsa_da;
+                       fr_dict_attr_t const    *vendor_da;
+                       fr_dict_attr_t          *new;
+                       fr_dict_attr_t          *mutable;
 
-       return found->vendorpec;
-}
+                       if (argc < 2) {
+                               fr_strerror_printf_push("Invalid BEGIN-VENDOR entry");
+                               goto error;
+                       }
 
-/** Look up a vendor by its PEN
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] vendorpec                to search for.
- * @return
- *     - The vendor.
- *     - NULL if no vendor with that number was regitered for this protocol.
- */
-fr_dict_vendor_t const *fr_dict_vendor_by_num(fr_dict_t const *dict, int vendorpec)
-{
-       fr_dict_vendor_t dv;
+                       vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
+                       if (!vendor) {
+                               fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
+                               goto error;
+                       }
 
-       INTERNAL_IF_NULL(dict);
+                       /*
+                        *      Check for extended attr VSAs
+                        *
+                        *      BEGIN-VENDOR foo format=Foo-Encapsulation-Attr
+                        */
+                       if (argc > 2) {
+                               if (strncmp(argv[2], "format=", 7) != 0) {
+                                       fr_strerror_printf_push("Invalid format %s", argv[2]);
+                                       goto error;
+                               }
 
-       dv.vendorpec = vendorpec;
+                               p = argv[2] + 7;
+                               da = fr_dict_attr_by_name(ctx->dict, p);
+                               if (!da) {
+                                       fr_strerror_printf_push("Invalid format for BEGIN-VENDOR: Unknown "
+                                                               "attribute '%s'", p);
+                                       goto error;
+                               }
 
-       return fr_hash_table_finddata(dict->vendors_by_num, &dv);
-}
+                               if (da->type != FR_TYPE_EVS) {
+                                       fr_strerror_printf_push("Invalid format for BEGIN-VENDOR.  "
+                                                               "Attribute '%s' should be 'evs' but is '%s'", p,
+                                                               fr_int2str(dict_attr_types, da->type, "?Unknown?"));
+                                       goto error;
+                               }
 
-/** Look up a vendor by one of its child attributes
- *
- * @param[in] da       The vendor attribute.
- * @return
- *     - The vendor.
- *     - NULL if no vendor with that number was regitered for this protocol.
- */
-fr_dict_vendor_t const *fr_dict_vendor_by_da(fr_dict_attr_t const *da)
-{
-       fr_dict_t               *dict;
-       fr_dict_vendor_t        dv;
+                               vsa_da = da;
+                       } else {
+                               /*
+                                *      Automagically create Attribute 26
+                                *
+                                *      This should exist, but in case we're starting without
+                                *      the RFC dictionaries we need to add it in the case
+                                *      it doesn't.
+                                */
+                               vsa_da = fr_dict_attr_child_by_num(ctx->parent, FR_VENDOR_SPECIFIC);
+                               if (!vsa_da) {
+                                       memset(&flags, 0, sizeof(flags));
 
-       dv.vendorpec = fr_dict_vendor_num_by_da(da);
-       if (!dv.vendorpec) return NULL;
+                                       memcpy(&mutable, &ctx->parent, sizeof(mutable));
+                                       new = dict_attr_alloc(mutable, fr_dict_root(ctx->dict), "Vendor-Specific",
+                                                                FR_VENDOR_SPECIFIC, FR_TYPE_VSA, &flags);
+                                       dict_attr_child_add(mutable, new);
+                                       vsa_da = new;
+                               }
+                       }
 
-       dict = fr_dict_by_da(da);
+                       /*
+                        *      Create a VENDOR attribute on the fly, either in the context
+                        *      of the EVS attribute, or the VSA (26) attribute.
+                        */
+                       vendor_da = fr_dict_attr_child_by_num(vsa_da, vendor);
+                       if (!vendor_da) {
+                               memset(&flags, 0, sizeof(flags));
 
-       return fr_hash_table_finddata(dict->vendors_by_num, &dv);
-}
+                               if (vsa_da->type == FR_TYPE_VSA) {
+                                       fr_dict_vendor_t const *dv;
 
-/** Return the vendor that parents this attribute
- *
- * @note Uses the dictionary hierachy to determine the parent
- *
- * @param[in] da               The dictionary attribute to find parent for.
- * @return
- *     - NULL if the attribute has no vendor.
- *     - A fr_dict_attr_t representing this attribute's associated vendor.
- */
-fr_dict_attr_t const *fr_dict_vendor_attr_by_da(fr_dict_attr_t const *da)
-{
-       fr_dict_attr_t const *da_p = da;
+                                       dv = fr_dict_vendor_by_num(ctx->dict, vendor);
+                                       if (dv) {
+                                               flags.type_size = dv->type;
+                                               flags.length = dv->length;
 
-       VERIFY_DA(da);
+                                       } else { /* unknown vendor, shouldn't happen */
+                                               flags.type_size = 1;
+                                               flags.length = 1;
+                                       }
 
-       while (da_p->parent) {
-               if (da_p->type == FR_TYPE_VENDOR) break;
-               da_p = da_p->parent;
-       }
-       if (da_p->type != FR_TYPE_VENDOR) return NULL;
+                               } else { /* EVS are always "format=1,1" */
+                                       flags.type_size = 1;
+                                       flags.length = 1;
+                               }
 
-       return da_p;
-}
+                               memcpy(&mutable, &vsa_da, sizeof(mutable));
+                               new = dict_attr_alloc(mutable, ctx->parent, argv[1], vendor, FR_TYPE_VENDOR, &flags);
+                               dict_attr_child_add(mutable, new);
 
-/** Return vendor attribute for the specified dictionary and vendorpec
- *
- * @param[in] dict             to search for the vendor in.
- * @param[in] vendor_root      of the vendor root attribute.  Could be 26 (for example) in RADIUS.
- * @param[in] vendor           to find.
- * @return
- *     - NULL if vendor does not exist.
- *     - A fr_dict_attr_t representing the vendor in the dictionary hierarchy.
- */
-fr_dict_attr_t const *fr_dict_vendor_attr_by_num(fr_dict_t const *dict, unsigned int vendor_root, unsigned int vendor)
-{
-       fr_dict_attr_t const *da;
+                               vendor_da = new;
+                       }
+                       ctx->parent = vendor_da;
+                       ctx->block_vendor = vendor;
+                       continue;
+               } /* BEGIN-VENDOR */
 
-       if (!dict) return NULL;
+               if (strcasecmp(argv[0], "END-VENDOR") == 0) {
+                       unsigned int vendor;
 
-       da = fr_dict_attr_child_by_num(fr_dict_root(dict), vendor_root);
-       if (!da) {
-               fr_strerror_printf("Vendor root attribute %i not defined in dict %s", vendor_root, dict->root->name);
-               return NULL;
-       }
+                       if (argc != 2) {
+                               fr_strerror_printf_push("Invalid END-VENDOR entry");
+                               goto error;
+                       }
 
-       switch (da->type) {
-       case FR_TYPE_VSA:       /* Vendor specific attribute */
-       case FR_TYPE_EVS:       /* Extended vendor specific attribute */
-               break;
+                       vendor = fr_dict_vendor_by_name(ctx->dict, argv[1]);
+                       if (!vendor) {
+                               fr_strerror_printf_push("Unknown vendor '%s'", argv[1]);
+                               goto error;
+                       }
 
-       default:
-               fr_strerror_printf("Wrong type for vendor root, expected '%s' or '%s' got '%s'",
-                                  fr_int2str(dict_attr_types, FR_TYPE_VSA, "<INVALID>"),
-                                  fr_int2str(dict_attr_types, FR_TYPE_EVS, "<INVALID>"),
-                                  fr_int2str(dict_attr_types, da->type, "<INVALID>"));
-               return NULL;
-       }
+                       if (vendor != ctx->block_vendor) {
+                               fr_strerror_printf_push("END-VENDOR '%s' does not match any previous BEGIN-VENDOR",
+                                                  argv[1]);
+                               goto error;
+                       }
+                       ctx->parent = ctx->dict->root;
+                       ctx->block_vendor = 0;
+                       continue;
+               } /* END-VENDOR */
 
-       da = fr_dict_attr_child_by_num(da, vendor);
-       if (!da) {
-               fr_strerror_printf("Vendor %i not defined", vendor);
-               return NULL;
+               /*
+                *      Any other string: We don't recognize it.
+                */
+               fr_strerror_printf_push("Invalid keyword '%s'", argv[0]);
+               goto error;
        }
+       fclose(fp);
+       return 0;
+}
 
-       if (da->type != FR_TYPE_VENDOR) {
-               fr_strerror_printf("Wrong type for vendor, expected '%s' got '%s'",
-                                  fr_int2str(dict_attr_types, da->type, "<INVALID>"),
-                                  fr_int2str(dict_attr_types, FR_TYPE_VENDOR, "<INVALID>"));
-               return NULL;
-       }
+static int dict_from_file(fr_dict_t *dict,
+                         char const *dir_name, char const *filename,
+                         char const *src_file, int src_line)
+{
+       dict_from_file_ctx_t    ctx = {
+                                       .dict = dict,
+                                       .parent = dict->root
+                               };
 
-       return da;
+       return _dict_from_file(&ctx, dir_name, filename, src_file, src_line);
 }
 
-/** Look up a dictionary attribute by a name embedded in another string
- *
- * Find the first invalid attribute name char in the string pointed
- * to by name.
- *
- * Copy the characters between the start of the name string and the first
- * none #fr_dict_attr_allowed_chars char to a buffer and perform a dictionary lookup
- * using that value.
+/** (re)initialize a protocol dictionary
  *
- * If the attribute exists, advance the pointer pointed to by name
- * to the first none #fr_dict_attr_allowed_chars char, and return the DA.
+ * Initialize the directory, then fix the attr member of all attributes.
  *
- * If the attribute does not exist, don't advance the pointer and return
- * NULL.
+ * First dictionary initialised will be set as the default internal dictionary.
  *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in,out] name         string start.
+ * @param[in] ctx              to allocate the dictionary from.
+ * @param[out] out             Where to write a pointer to the new dictionary.
+ *                             Will free existing dictionary if files have
+ *                             changed and *out is not NULL.
+ * @param[in] dir              to read dictionary files from.
+ * @param[in] fn               file name to read.
+ * @param[in] name             to use for the root attributes.
  * @return
- *     - Attribute matching name.
- *     - NULL if no matching attribute could be found.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-fr_dict_attr_t const *fr_dict_attr_by_name_substr(fr_dict_t const *dict, char const **name)
+int fr_dict_from_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *fn, char const *name)
 {
-       fr_dict_attr_t          find;
-       fr_dict_attr_t const    *da;
-       char const              *p;
-       size_t                  len;
+       static bool     defined_cast_types;
+       fr_dict_t       *dict;
 
-       if (!name || !*name) return NULL;
-       INTERNAL_IF_NULL(dict);
+       dict = dict_alloc(ctx);
+       if (!dict) return -1;
 
-       memset(&find, 0, sizeof(find));
+       /*
+        *      Free the old dictionaries
+        */
+       if (*out == fr_dict_internal) fr_dict_internal = dict;
+       TALLOC_FREE(*out);
 
        /*
-        *      Advance p until we get something that's not part of
-        *      the dictionary attribute name.
+        *      Remove this at some point...
         */
-       for (p = *name; fr_dict_attr_allowed_chars[(int)*p]; p++);
+       if (!fr_dict_internal) fr_dict_internal = dict;
 
-       len = p - *name;
-       if (len > FR_DICT_ATTR_MAX_NAME_LEN) {
-               fr_strerror_printf("Attribute name too long");
-               return NULL;
-       }
+       /*
+        *      Magic dictionary root attribute
+        */
+       dict_root_set(dict, name, 0);
 
-       find.name = talloc_bstrndup(NULL, *name, len);
-       if (!find.name) {
-               fr_strerror_printf("Out of memory");
-               return NULL;
-       }
-       da = fr_hash_table_finddata(dict->attributes_by_name, &find);
-       talloc_const_free(find.name);
+       /*
+        *      Add cast attributes.  We do it this way,
+        *      so cast attributes get added automatically for new types.
+        *
+        *      We manually add the attributes to the dictionary, and bypass
+        *      fr_dict_attr_add(), because we know what we're doing, and
+        *      that function does too many checks.
+        */
+       if (!defined_cast_types) {
+               FR_NAME_NUMBER const    *p;
+               fr_dict_attr_flags_t    flags;
+               char                    *type_name;
 
-       if (!da) {
-               fr_strerror_printf("Unknown attribute '%.*s'", (int) len, *name);
-               return NULL;
+               memset(&flags, 0, sizeof(flags));
+
+               flags.internal = 1;
+
+               for (p = dict_attr_types; p->name; p++) {
+                       fr_dict_attr_t *n;
+
+                       type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+
+                       n = dict_attr_alloc(dict->pool, dict->root, type_name,
+                                           FR_CAST_BASE + p->number, p->number, &flags);
+                       if (!n) {
+                       error:
+                               talloc_free(dict);
+                               return -1;
+                       }
+
+                       if (!fr_hash_table_insert(dict->attributes_by_name, n)) goto error;
+
+                       /*
+                        *      Set up parenting for the attribute.
+                        */
+                       if (dict_attr_child_add(dict->root, n) < 0) goto error;
+
+                       talloc_free(type_name);
+               }
+               defined_cast_types = true;
        }
-       *name = p;
 
-       return da;
-}
+       if (dict_from_file(dict, dir, fn, NULL, 0) < 0) goto error;
 
-/** Locate a #fr_dict_attr_t by its name
- *
- * @note Unlike attribute numbers, attribute names are unique to the dictionary.
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] name             of the attribute to locate.
- * @return
- *     - Attribute matching name.
- *     - NULL if no matching attribute could be found.
- */
-fr_dict_attr_t const *fr_dict_attr_by_name(fr_dict_t const *dict, char const *name)
-{
-       fr_dict_attr_t find = { .name = name };
+       /*
+        *      Resolve any VALUE aliases (enums) that were defined
+        *      before the attributes they reference.
+        */
+       if (dict->enum_fixup) {
+               fr_dict_attr_t const *da;
+               dict_enum_fixup_t *this, *next;
 
-       if (!name) return NULL;
-       INTERNAL_IF_NULL(dict);
+               for (this = dict->enum_fixup; this != NULL; this = next) {
+                       fr_value_box_t  value;
+                       fr_type_t       type;
 
-       return fr_hash_table_finddata(dict->attributes_by_name, &find);
-}
+                       next = this->next;
+                       da = fr_dict_attr_by_name(dict, this->attribute);
+                       if (!da) {
+                               fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
+                                                  this->attribute, this->alias);
+                               goto error;
+                       }
+                       type = da->type;
 
-/** Lookup a #fr_dict_attr_t by its vendor and attribute numbers
- *
- * @note This is a deprecated function, new code should use #fr_dict_attr_child_by_num.
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] vendor           number of the attribute.
- * @param[in] attr             number of the attribute.
- * @return
- *     - Attribute matching vendor/attr.
- *     - NULL if no matching attribute could be found.
- */
-fr_dict_attr_t const *fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
-{
-       fr_dict_attr_t const *parent;
+                       if (fr_value_box_from_str(this, &value, &type, NULL,
+                                                 this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
+                               fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+                               goto error;
+                       }
 
-       INTERNAL_IF_NULL(dict);
+                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
 
-       if (vendor == 0) return fr_dict_attr_child_by_num(dict->root, attr);
+                       /*
+                        *      Just so we don't lose track of things.
+                        */
+                       dict->enum_fixup = next;
+               }
+       }
 
-       parent = fr_dict_attr_child_by_num(dict->root, FR_VENDOR_SPECIFIC);
-       if (!parent) return NULL;
+       /*
+        *      Walk over all of the hash tables to ensure they're
+        *      initialized.  We do this because the threads may perform
+        *      lookups, and we don't want multi-threaded re-ordering
+        *      of the table entries.  That would be bad.
+        */
+       fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
 
-       parent = fr_dict_attr_child_by_num(parent, vendor);
-       if (!parent) return NULL;
+       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
 
-       return fr_dict_attr_child_by_num(parent, attr);
+       *out = dict;
+
+       return 0;
 }
 
-/** Lookup a attribute by its its vendor and attribute numbers and data type
+/** (Re-)Initialize the special internal dictionary
  *
- * @note Only works with FR_TYPE_COMBO_IP
+ * This dictionary has additional programatically generated attributes added to it.
  *
- * @param[in] da               to look for type variant of.
- * @param[in] type             Variant of attribute to lookup.
+ * @param[in] ctx              to allocate dictionary in.
+ * @param[out] out             Where to write pointer to the internal dictionary.
+ * @param[in] dir              dictionary is located in.
+ * @param[in] internal_name    name of the internal dictionary dir (may be NULL).
  * @return
- *     - Attribute matching parent/attr/type.
- *     - NULL if no matching attribute could be found.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-fr_dict_attr_t const *fr_dict_attr_by_type(fr_dict_attr_t const *da, fr_type_t type)
+int fr_dict_internal_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out, char const *dir, char const *internal_name)
 {
-       fr_dict_t       *dict = fr_dict_by_da(da);
-       fr_dict_attr_t  find = {
-                               .parent = da->parent,
-                               .attr = da->attr,
-                               .type = type
-                       };
-
-       return fr_hash_table_finddata(dict->attributes_combo, &find);
-}
+       fr_dict_t               *dict = fr_dict_internal;
+       char                    *dict_dir;
+       char                    *tmp;
+       FR_NAME_NUMBER const    *p;
+       fr_dict_attr_flags_t    flags = { .internal = true };
+       char                    *type_name;
 
-/** Check if a child attribute exists in a parent using a pointer (da)
- *
- * @param[in] parent           to check for child in.
- * @param[in] child            to look for.
- * @return
- *     - The child attribute on success.
- *     - NULL if the child attribute does not exist.
- */
-inline fr_dict_attr_t const *fr_dict_attr_child_by_da(fr_dict_attr_t const *parent, fr_dict_attr_t const *child)
-{
-       fr_dict_attr_t const *bin;
+       memcpy(&tmp, &dir, sizeof(tmp));
+       dict_dir = internal_name ? talloc_asprintf(NULL, "%s%c%s", dir, FR_DIR_SEP, internal_name) : tmp;
 
-       VERIFY_DA(parent);
+       if ((!protocol_by_name || !protocol_by_num) && (dict_global_init(ctx) < 0)) return -1;
 
-       if (!parent->children) return NULL;
+       if (!dict) {
+               dict = dict_alloc(ctx);
+               if (!dict) {
+               error:
+                       if (!fr_dict_internal) talloc_free(dict);
+                       if (internal_name) talloc_free(dict_dir);
+                       return -1;
+               }
 
+               /*
+                *      Set the root name of the dictionary
+                */
+               dict_root_set(dict, "internal", 0);
+       } else {
+               if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) {
+                       if (internal_name) talloc_free(dict_dir);
+                       return 0;
+               }
+       }
        /*
-        *      Only some types can have children
+        *      Add cast attributes.  We do it this way,
+        *      so cast attributes get added automatically for new types.
+        *
+        *      We manually add the attributes to the dictionary, and bypass
+        *      fr_dict_attr_add(), because we know what we're doing, and
+        *      that function does too many checks.
         */
-       switch (parent->type) {
-       default:
-               return NULL;
+       for (p = dict_attr_types; p->name; p++) {
+               fr_dict_attr_t *n;
+
+               type_name = talloc_typed_asprintf(dict->pool, "Tmp-Cast-%s", p->name);
+
+               n = dict_attr_alloc(dict->pool, dict->root, type_name,
+                                   FR_CAST_BASE + p->number, p->number, &flags);
+               if (!n) goto error;
+
+               if (!fr_hash_table_insert(dict->attributes_by_name, n)) {
+                       fr_strerror_printf("Failed inserting \"%s\" into internal dictionary", type_name);
+                       goto error;
+               }
 
-       case FR_TYPE_STRUCTURAL:
-               break;
+               /*
+                *      Set up parenting for the attribute.
+                */
+               if (dict_attr_child_add(dict->root, n) < 0) goto error;
+
+               talloc_free(type_name);
        }
 
-       /*
-        *      Child arrays may be trimmed back to save memory.
-        *      Check that so we don't SEGV.
-        */
-       if ((child->attr & 0xff) > talloc_array_length(parent->children)) return NULL;
+       if (dict_dir && dict_from_file(dict, dict_dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
 
-       bin = parent->children[child->attr & 0xff];
-       for (;;) {
-               if (!bin) return NULL;
-               if (bin == child) return bin;
-               bin = bin->next;
-       }
+       *out = dict;
+       if (!fr_dict_internal) fr_dict_internal = dict;
 
-       return NULL;
+       return 0;
 }
 
-/** Check if a child attribute exists in a parent using an attribute number
+/** (Re)-initialize a protocol dictionary
  *
- * @param[in] parent           to check for child in.
- * @param[in] attr             number to look for.
+ * Initialize the directory, then fix the attr member of all attributes.
+ *
+ * First dictionary initialised will be set as the default internal dictionary.
+ *
+ * @param[in] ctx              to allocate the dictionary from.
+ * @param[out] out             Where to write a pointer to the new dictionary.  Will free existing
+ *                             dictionary if files have changed and *out is not NULL.
+ * @param[in] base_dir         containing all the protocol directories.
+ * @param[in] proto_name       that we're loading the dictionary for.
  * @return
- *     - The child attribute on success.
- *     - NULL if the child attribute does not exist.
+ *     - 0 on success.
+ *     - -1 on failure.
  */
-inline fr_dict_attr_t const *fr_dict_attr_child_by_num(fr_dict_attr_t const *parent, unsigned int attr)
+int fr_dict_protocol_afrom_file(TALLOC_CTX *ctx, fr_dict_t **out,
+                               char const *base_dir, char const *proto_name)
 {
-       fr_dict_attr_t const *bin;
-
-       VERIFY_DA(parent);
+       fr_dict_t       *dict;
+       char            *dir;
+       char            *proto_dir;
+       char            *p;
 
-       if (!parent->children) return NULL;
+       if (!protocol_by_name || !protocol_by_num) {
+               fr_strerror_printf("Dictionary not yet initialized call fr_dict_internal_afrom_file first");
+               return -1;
+       }
 
        /*
-        *      Only some types can have children
+        *      Increment the reference count if the dictionary
+        *      has already been loaded.
         */
-       switch (parent->type) {
-       default:
-               return NULL;
-
-       case FR_TYPE_STRUCTURAL:
-               break;
+       if (!*out) {
+               *out = fr_dict_by_protocol_name(proto_name);
+               if (*out) {
+                        talloc_increase_ref_count(*out);
+                        return 0;
+               }
        }
 
        /*
-        *      Child arrays may be trimmed back to save memory.
-        *      Check that so we don't SEGV.
+        *      Replace '_' with '/'
         */
-       if ((attr & 0xff) > talloc_array_length(parent->children)) return NULL;
+       proto_dir = talloc_strdup(ctx, proto_name);
+       for (p = proto_dir; *p; p++) if (*p == '_') *p = FR_DIR_SEP;
 
-       bin = parent->children[attr & 0xff];
-       for (;;) {
-               if (!bin) return NULL;
-               if (bin->attr == attr) return bin;
-               bin = bin->next;
+       dir = talloc_asprintf(proto_dir, "%s%c%s", base_dir, FR_DIR_SEP, proto_dir);
+       if (!*out) {
+               dict = dict_alloc(ctx);
+               if (!dict) {
+               error:
+                       talloc_free(proto_dir);
+                       return -1;
+               }
+       } else {
+               dict = *out;
+               if (dict_stat_check(dict, dir, FR_DICTIONARY_FILE)) return 0;
        }
 
-       return NULL;
-}
-
-/** Lookup the structure representing an enum value in a #fr_dict_attr_t
- *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] da               to search in.
- * @param[in] value            to search for.
- * @return
- *     - Matching #fr_dict_enum_t.
- *     - NULL if no matching #fr_dict_enum_t could be found.
- */
-fr_dict_enum_t *fr_dict_enum_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
-{
-       fr_dict_enum_t enumv, *dv;
+       dict->enum_fixup = NULL;        /* just to be safe. */
 
-       if (!da) return NULL;
+       if (dict_from_file(dict, dir, FR_DICTIONARY_FILE, NULL, 0) < 0) goto error;
 
-       INTERNAL_IF_NULL(dict);
+       talloc_free(proto_dir);
 
        /*
-        *      Could be NULL or an unknown attribute, in which case
-        *      we want to avoid the lookup gracefully...
+        *      Resolve any VALUE aliases (enums) that were defined
+        *      before the attributes they reference.
         */
-       if (value->type != da->type) return NULL;
+       if (dict->enum_fixup) {
+               fr_dict_attr_t const *da;
+               dict_enum_fixup_t *this, *next;
 
-       /*
-        *      First, look up aliases.
-        */
-       enumv.da = da;
-       enumv.alias = "";
+               for (this = dict->enum_fixup; this != NULL; this = next) {
+                       fr_value_box_t  value;
+                       fr_type_t       type;
+
+                       next = this->next;
+                       da = fr_dict_attr_by_name(dict, this->attribute);
+                       if (!da) {
+                               fr_strerror_printf("No ATTRIBUTE '%s' defined for VALUE '%s'",
+                                                  this->attribute, this->alias);
+                               goto error;
+                       }
+                       type = da->type;
+
+                       if (fr_value_box_from_str(this, &value, &type, NULL,
+                                                 this->value, talloc_array_length(this->value) - 1, '\0', false) < 0) {
+                               fr_strerror_printf_push("Invalid VALUE for ATTRIBUTE \"%s\"", da->name);
+                               goto error;
+                       }
+
+                       if (fr_dict_enum_add_alias(da, this->alias, &value, false, false) < 0) goto error;
+
+                       /*
+                        *      Just so we don't lose track of things.
+                        */
+                       dict->enum_fixup = next;
+               }
+       }
 
        /*
-        *      Look up the attribute alias target, and use
-        *      the correct attribute number if found.
+        *      Walk over all of the hash tables to ensure they're
+        *      initialized.  We do this because the threads may perform
+        *      lookups, and we don't want multi-threaded re-ordering
+        *      of the table entries.  That would be bad.
         */
-       dv = fr_hash_table_finddata(dict->values_by_alias, &enumv);
-       if (dv) enumv.da = dv->da;
+       fr_hash_table_walk(dict->vendors_by_name, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->vendors_by_num, hash_null_callback, NULL);
 
-       enumv.value = value;
+       fr_hash_table_walk(dict->values_by_da, hash_null_callback, NULL);
+       fr_hash_table_walk(dict->values_by_alias, hash_null_callback, NULL);
 
-       return fr_hash_table_finddata(dict->values_by_da, &enumv);
+       *out = dict;
+
+       return 0;
 }
 
-/** Lookup the name of an enum value in a #fr_dict_attr_t
+/** Read supplementary attribute definitions into an existing dictionary
  *
- * @param[in] dict             of protocol context we're operating in.
- *                             If NULL the internal dictionary will be used.
- * @param[in] da               to search in.
- * @param[in] value            number to search for.
- * @return
- *     - Name of value.
- *     - NULL if no matching value could be found.
+ * @param[in] dict     Existing dictionary.
+ * @param[in] dir      dictionary is located in.
+ * @param[in] filename of the dictionary.
  */
-char const *fr_dict_enum_alias_by_value(fr_dict_t *dict, fr_dict_attr_t const *da, fr_value_box_t const *value)
+int fr_dict_read(fr_dict_t *dict, char const *dir, char const *filename)
 {
-       fr_dict_enum_t *dv;
+       INTERNAL_IF_NULL(dict);
 
-       if (!da) return NULL;
+       if (!dict->attributes_by_name) {
+               fr_strerror_printf("%s: Must call fr_dict_from_file() before fr_dict_read()", __FUNCTION__);
+               return -1;
+       }
 
-       INTERNAL_IF_NULL(dict);
+       return dict_from_file(dict, dir, filename, NULL, 0);
+}
 
-       dv = fr_dict_enum_by_value(dict, da, value);
-       if (!dv) return "";
+static void _fr_dict_dump(fr_dict_attr_t const *da, unsigned int lvl)
+{
+       unsigned int            i;
+       size_t                  len;
+       fr_dict_attr_t const    *p;
 
-       return dv->alias;
+       printf("%p - %s (%u) %s\n", da, da->name, da->attr, fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+
+       len = talloc_array_length(da->children);
+       for (i = 0; i < len; i++) {
+               for (p = da->children[i]; p; p = p->next) {
+                       _fr_dict_dump(p, lvl + 1);
+               }
+       }
+
+}
+
+void fr_dict_dump(fr_dict_t *dict)
+{
+       _fr_dict_dump(dict->root, 0);
 }
 
 /*
- *     Get a value by its name, keyed off of an attribute.
+ *     External API for testing
  */
-fr_dict_enum_t *fr_dict_enum_by_alias(fr_dict_t *dict, fr_dict_attr_t const *da, char const *alias)
+int fr_dict_parse_str(fr_dict_t *dict, char *buf, fr_dict_attr_t const *parent, unsigned int vendor)
 {
-       fr_dict_enum_t find, *found;
+       int     argc;
+       char    *argv[MAX_ARGV];
+       fr_dict_attr_flags_t base_flags;
 
-       memset(&find, 0, sizeof(find));
+       INTERNAL_IF_NULL(dict);
 
-       if (!alias) return NULL;
+       argc = fr_dict_str_to_argv(buf, argv, MAX_ARGV);
+       if (argc == 0) return 0;
 
-       INTERNAL_IF_NULL(dict);
+       if (strcasecmp(argv[0], "VALUE") == 0) {
+               return dict_read_process_value(dict, argv + 1, argc - 1);
+       }
 
-       find.da = da;
-       find.alias = alias;
+       if (strcasecmp(argv[0], "ATTRIBUTE") == 0) {
+               if (!parent) parent = fr_dict_root(dict);
 
-       /*
-        *      Look up the attribute alias target, and use
-        *      the correct attribute number if found.
-        */
-       found = fr_hash_table_finddata(dict->values_by_alias, &find);
-       if (found) find.da = found->da;
+               memset(&base_flags, 0, sizeof(base_flags));
 
-       return fr_hash_table_finddata(dict->values_by_alias, &find);
+               return dict_read_process_attribute(dict, parent, vendor, argv + 1, argc - 1, &base_flags);
+       }
+
+       if (strcasecmp(argv[0], "VENDOR") == 0) {
+               return dict_read_process_vendor(dict, argv + 1, argc - 1);
+       }
+
+       fr_strerror_printf("Invalid input '%s'", argv[0]);
+       return -1;
 }
 
 /*