fix printing of long hex values
[freeradius.git] / src / lib / value.c
index 551ac52..ca18327 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * valuepair.c Functions to handle value_data_t
+ * value.c     Functions to handle value_data_t
  *
  * Version:    $Id$
  *
@@ -35,8 +35,8 @@ RCSID("$Id$")
  * @param[in] b Value to compare.
  * @return -1 if a is less than b, 0 if both are equal, 1 if a is more than b, < -1 on error.
  */
-int value_data_cmp(PW_TYPE a_type, size_t a_len, value_data_t const *a,
-                  PW_TYPE b_type, size_t b_len, value_data_t const *b)
+int value_data_cmp(PW_TYPE a_type, value_data_t const *a, size_t a_len,
+                  PW_TYPE b_type, value_data_t const *b, size_t b_len)
 {
        int compare = 0;
 
@@ -56,7 +56,7 @@ int value_data_cmp(PW_TYPE a_type, size_t a_len, value_data_t const *a,
        {
                size_t length;
 
-               if (a_len > b_len) {
+               if (a_len < b_len) {
                        length = a_len;
                } else {
                        length = b_len;
@@ -110,7 +110,7 @@ int value_data_cmp(PW_TYPE a_type, size_t a_len, value_data_t const *a,
                break;
 
        case PW_TYPE_ETHERNET:
-               compare = memcmp(&a->ether, &b->ether, sizeof(a->ether));
+               compare = memcmp(a->ether, b->ether, sizeof(a->ether));
                break;
 
        case PW_TYPE_IPV4_ADDR: {
@@ -131,15 +131,15 @@ int value_data_cmp(PW_TYPE a_type, size_t a_len, value_data_t const *a,
                break;
 
        case PW_TYPE_IPV6_PREFIX:
-               compare = memcmp(&a->ipv6prefix, &b->ipv6prefix, sizeof(a->ipv6prefix));
+               compare = memcmp(a->ipv6prefix, b->ipv6prefix, sizeof(a->ipv6prefix));
                break;
 
        case PW_TYPE_IPV4_PREFIX:
-               compare = memcmp(&a->ipv4prefix, &b->ipv4prefix, sizeof(a->ipv4prefix));
+               compare = memcmp(a->ipv4prefix, b->ipv4prefix, sizeof(a->ipv4prefix));
                break;
 
        case PW_TYPE_IFID:
-               compare = memcmp(&a->ifid, &b->ifid, sizeof(a->ifid));
+               compare = memcmp(a->ifid, b->ifid, sizeof(a->ifid));
                break;
 
        /*
@@ -147,7 +147,7 @@ int value_data_cmp(PW_TYPE a_type, size_t a_len, value_data_t const *a,
         */
        case PW_TYPE_INVALID:           /* We should never see these */
        case PW_TYPE_COMBO_IP_ADDR:             /* This should have been converted into IPADDR/IPV6ADDR */
-       case PW_TYPE_IP_PREFIX:         /* This should have been converted into IPADDR/IPV6ADDR */
+       case PW_TYPE_COMBO_IP_PREFIX:           /* This should have been converted into IPADDR/IPV6ADDR */
        case PW_TYPE_TLV:
        case PW_TYPE_EXTENDED:
        case PW_TYPE_LONG_EXTENDED:
@@ -297,8 +297,8 @@ static int value_data_cidr_cmp_op(FR_TOKEN op, int bytes,
  * @return 1 if true, 0 if false, -1 on error.
  */
 int value_data_cmp_op(FR_TOKEN op,
-                     PW_TYPE a_type, size_t a_len, value_data_t const *a,
-                     PW_TYPE b_type, size_t b_len, value_data_t const *b)
+                     PW_TYPE a_type, value_data_t const *a, size_t a_len,
+                     PW_TYPE b_type, value_data_t const *b, size_t b_len)
 {
        int compare = 0;
 
@@ -312,31 +312,29 @@ int value_data_cmp_op(FR_TOKEN op,
 
                case PW_TYPE_IPV4_PREFIX:       /* IPv4 and IPv4 Prefix */
                        return value_data_cidr_cmp_op(op, 4, 32, (uint8_t const *) &a->ipaddr,
-                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix + 2);
+                                                     b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV4_PREFIX:               /* IPv4 and IPv4 Prefix */
                switch (b_type) {
                case PW_TYPE_IPV4_ADDR:
                        return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1],
-                                                   (uint8_t const *) &a->ipv4prefix + 2,
+                                                   (uint8_t const *) &a->ipv4prefix[2],
                                                    32, (uint8_t const *) &b->ipaddr);
 
                case PW_TYPE_IPV4_PREFIX:       /* IPv4 Prefix and IPv4 Prefix */
                        return value_data_cidr_cmp_op(op, 4, a->ipv4prefix[1],
-                                                   (uint8_t const *) &a->ipv4prefix + 2,
-                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix + 2);
+                                                   (uint8_t const *) &a->ipv4prefix[2],
+                                                   b->ipv4prefix[1], (uint8_t const *) &b->ipv4prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv4 with IPv6 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV6_ADDR:
                switch (b_type) {
@@ -345,37 +343,34 @@ int value_data_cmp_op(FR_TOKEN op,
 
                case PW_TYPE_IPV6_PREFIX:       /* IPv6 and IPv6 Preifx */
                        return value_data_cidr_cmp_op(op, 16, 128, (uint8_t const *) &a->ipv6addr,
-                                                   b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix + 2);
-                       break;
+                                                     b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
                        return -1;
                }
-               break;
 
        case PW_TYPE_IPV6_PREFIX:
                switch (b_type) {
                case PW_TYPE_IPV6_ADDR:         /* IPv6 Prefix and IPv6 */
                        return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1],
-                                                   (uint8_t const *) &a->ipv6prefix + 2,
-                                                   128, (uint8_t const *) &b->ipv6addr);
+                                                     (uint8_t const *) &a->ipv6prefix[2],
+                                                     128, (uint8_t const *) &b->ipv6addr);
 
                case PW_TYPE_IPV6_PREFIX:       /* IPv6 Prefix and IPv6 */
                        return value_data_cidr_cmp_op(op, 16, a->ipv6prefix[1],
-                                                   (uint8_t const *) &a->ipv6prefix + 2,
-                                                   b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix + 2);
+                                                     (uint8_t const *) &a->ipv6prefix[2],
+                                                     b->ipv6prefix[1], (uint8_t const *) &b->ipv6prefix[2]);
 
                default:
                        fr_strerror_printf("Cannot compare IPv6 with IPv4 address");
                        return -1;
                }
-               break;
 
        default:
        cmp:
-               compare = value_data_cmp(a_type, a_len, a,
-                                      b_type, b_len, b);
+               compare = value_data_cmp(a_type, a, a_len,
+                                        b_type, b, b_len);
                if (compare < -1) {     /* comparison error */
                        return -1;
                }
@@ -413,121 +408,187 @@ static char const hextab[] = "0123456789abcdef";
 /** Convert string value to a value_data_t type
  *
  * @param[in] ctx to alloc strings in.
- * @param[out] out where to write parsed value.
- * @param[in,out] type of value data to create/type of value created.
- * @param[in] enumv DICT_ATTR with string aliases for integer values.
- * @param[in] value String to convert. Binary safe for variable length values if len is provided.
- * @param[in] inlen may be < 0 in which case strlen(len) is used to determine length, else inlen
+ * @param[out] dst where to write parsed value.
+ * @param[in,out] src_type of value data to create/type of value created.
+ * @param[in] src_enumv DICT_ATTR with string aliases for integer values.
+ * @param[in] src String to convert. Binary safe for variable length values if len is provided.
+ * @param[in] src_len may be < 0 in which case strlen(len) is used to determine length, else src_len
  *       should be the length of the string or sub string to parse.
+ * @param[in] quote quotation character used to drive de-escaping
  * @return length of data written to out or -1 on parse error.
  */
-ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
-                           PW_TYPE *type, DICT_ATTR const *enumv,
-                           char const *value, ssize_t inlen)
+ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst,
+                           PW_TYPE *src_type, DICT_ATTR const *src_enumv,
+                           char const *src, ssize_t src_len, char quote)
 {
        DICT_VALUE      *dval;
        size_t          len;
        ssize_t         ret;
        char            buffer[256];
 
-       if (!value) return -1;
+       if (!src) return -1;
 
-       len = (inlen < 0) ? strlen(value) : (size_t)inlen;
+       len = (src_len < 0) ? strlen(src) : (size_t)src_len;
 
        /*
         *      Set size for all fixed length attributes.
         */
-       ret = dict_attr_sizes[*type][1];        /* Max length */
+       ret = dict_attr_sizes[*src_type][1];    /* Max length */
 
        /*
-        *      It's a variable ret type so we just alloc a new buffer
+        *      It's a variable ret src_type so we just alloc a new buffer
         *      of size len and copy.
         */
-       switch (*type) {
+       switch (*src_type) {
        case PW_TYPE_STRING:
        {
-               size_t          p_len;
-               char const      *cp;
-               char            *p;
+               char            *p, *buff;
+               char const      *q;
                int             x;
 
+               buff = p = talloc_bstrndup(ctx, src, len);
+
                /*
-                *      Do escaping here
+                *      No de-quoting.  Just copy the string.
                 */
-               out->strvalue = p = talloc_memdup(ctx, value, len + 1);
-               p[len] = '\0';
-               talloc_set_type(p, char);
+               if (!quote) {
+                       ret = len;
+                       dst->strvalue = buff;
+                       goto finish;
+               }
 
-               cp = value;
-               p_len = 0;
-               while (*cp) {
-                       char c = *cp++;
+               /*
+                *      Do escaping for single quoted strings.  Only
+                *      single quotes get escaped.  Everything else is
+                *      left as-is.
+                */
+               if (quote == '\'') {
+                       q = p;
+
+                       while (q < (buff + len)) {
+                               /*
+                                *      The quotation character is escaped.
+                                */
+                               if ((q[0] == '\\') &&
+                                   (q[1] == quote)) {
+                                       *(p++) = quote;
+                                       q += 2;
+                                       continue;
+                               }
+
+                               /*
+                                *      Two backslashes get mangled to one.
+                                */
+                               if ((q[0] == '\\') &&
+                                   (q[1] == '\\')) {
+                                       *(p++) = '\\';
+                                       q += 2;
+                                       continue;
+                               }
+
+                               /*
+                                *      Not escaped, just copy it over.
+                                */
+                               *(p++) = *(q++);
+                       }
 
-                       if (c == '\\') switch (*cp) {
-                       case 'r':
-                               c = '\r';
-                               cp++;
-                               break;
-                       case 'n':
-                               c = '\n';
-                               cp++;
-                               break;
-                       case 't':
-                               c = '\t';
-                               cp++;
-                               break;
-                       case '"':
-                               c = '"';
-                               cp++;
-                               break;
-                       case '\'':
-                               c = '\'';
-                               cp++;
-                               break;
-                       case '\\':
-                               c = '\\';
-                               cp++;
-                               break;
-                       case '`':
-                               c = '`';
-                               cp++;
-                               break;
-                       case '\0':
-                               c = '\\'; /* no cp++ */
-                               break;
-                       default:
-                               if ((cp[0] >= '0') &&
-                                   (cp[0] <= '9') &&
-                                   (cp[1] >= '0') &&
-                                   (cp[1] <= '9') &&
-                                   (cp[2] >= '0') &&
-                                   (cp[2] <= '9') &&
-                                   (sscanf(cp, "%3o", &x) == 1)) {
-                                       c = x;
-                                       cp += 3;
-
-                               } else if (cp[0]) {
+                       *p = '\0';
+                       ret = p - buff;
+
+                       /* Shrink the buffer to the correct size */
+                       dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
+                       goto finish;
+               }
+
+               /*
+                *      It's "string" or `string`, do all standard
+                *      escaping.
+                */
+               q = p;
+               while (q < (buff + len)) {
+                       char c = *q++;
+
+                       if ((c == '\\') && (q >= (buff + len))) {
+                               fr_strerror_printf("Invalid escape at end of string");
+                               talloc_free(buff);
+                               return -1;
+                       }
+
+                       /*
+                        *      Fix up \X -> ... the binary form of it.
+                        */
+                       if (c == '\\') {
+                               switch (*q) {
+                               case 'r':
+                                       c = '\r';
+                                       q++;
+                                       break;
+
+                               case 'n':
+                                       c = '\n';
+                                       q++;
+                                       break;
+
+                               case 't':
+                                       c = '\t';
+                                       q++;
+                                       break;
+
+                               case '\\':
+                                       c = '\\';
+                                       q++;
+                                       break;
+
+                               default:
                                        /*
-                                        *      \p --> p
+                                        *      \" --> ", but only inside of double quoted strings, etc.
                                         */
-                                       c = *cp++;
-                               } /* else at EOL \ --> \ */
+                                       if (*q == quote) {
+                                               c = quote;
+                                               q++;
+                                               break;
+                                       }
+
+                                       /*
+                                        *      \000 --> binary zero character
+                                        */
+                                       if ((q[0] >= '0') &&
+                                           (q[0] <= '9') &&
+                                           (q[1] >= '0') &&
+                                           (q[1] <= '9') &&
+                                           (q[2] >= '0') &&
+                                           (q[2] <= '9') &&
+                                           (sscanf(q, "%3o", &x) == 1)) {
+                                               c = x;
+                                               q += 3;
+                                       }
+
+                                       /*
+                                        *      Else It's not a recognised escape sequence DON'T
+                                        *      consume the backslash. This is identical
+                                        *      behaviour to bash and most other things that
+                                        *      use backslash escaping.
+                                        */
+                               }
                        }
+
                        *p++ = c;
-                       p_len++;
                }
+
                *p = '\0';
-               ret = p_len;
+               ret = p - buff;
+               dst->strvalue = talloc_realloc(ctx, buff, char, ret + 1);
        }
                goto finish;
 
-       /* raw octets: 0x01020304... */
        case PW_TYPE_VSA:
-               if (strcmp(value, "ANY") == 0) {
-                       ret = 0;
-                       goto finish;
-               } /* else it's hex */
+               fr_strerror_printf("Must use 'Attr-26 = ...' instead of 'Vendor-Specific = ...'");
+               return -1;
 
+       /* raw octets: 0x01020304... */
+#ifndef WITH_ASCEND_BINARY
+       do_octets:
+#endif
        case PW_TYPE_OCTETS:
        {
                uint8_t *p;
@@ -535,46 +596,57 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                /*
                 *      No 0x prefix, just copy verbatim.
                 */
-               if ((len < 2) || (strncasecmp(value, "0x", 2) != 0)) {
-                       out->octets = talloc_memdup(ctx, (uint8_t const *)value, len);
-                       talloc_set_type(out->octets, uint8_t);
+               if ((len < 2) || (strncasecmp(src, "0x", 2) != 0)) {
+                       dst->octets = talloc_memdup(ctx, (uint8_t const *)src, len);
+                       talloc_set_type(dst->octets, uint8_t);
                        ret = len;
                        goto finish;
                }
 
-
-       do_octets:
                len -= 2;
 
                /*
                 *      Invalid.
                 */
                if ((len & 0x01) != 0) {
-                       fr_strerror_printf("Length of Hex String is not even, got %zu bytes", ret);
+                       fr_strerror_printf("Length of Hex String is not even, got %zu bytes", len);
                        return -1;
                }
 
                ret = len >> 1;
                p = talloc_array(ctx, uint8_t, ret);
-               if (fr_hex2bin(p, ret, value + 2, len) != (size_t)ret) {
+               if (fr_hex2bin(p, ret, src + 2, len) != (size_t)ret) {
                        talloc_free(p);
                        fr_strerror_printf("Invalid hex data");
                        return -1;
                }
 
-               out->octets = p;
+               dst->octets = p;
        }
                goto finish;
 
        case PW_TYPE_ABINARY:
 #ifdef WITH_ASCEND_BINARY
-               if ((len > 1) && (strncasecmp(value, "0x", 2) == 0)) goto do_octets;
+               if ((len > 1) && (strncasecmp(src, "0x", 2) == 0)) {
+                       ssize_t bin;
 
-               if (ascend_parse_filter(out, value, len) < 0 ) {
-                       /* Allow ascend_parse_filter's strerror to bubble up */
-                       return -1;
+                       if (len > ((sizeof(dst->filter) + 1) * 2)) {
+                               fr_strerror_printf("Hex data is too large for ascend filter");
+                               return -1;
+                       }
+
+                       bin = fr_hex2bin((uint8_t *) &dst->filter, ret, src + 2, len - 2);
+                       if (bin < ret) {
+                               memset(((uint8_t *) &dst->filter) + bin, 0, ret - bin);
+                       }
+               } else {
+                       if (ascend_parse_filter(dst, src, len) < 0 ) {
+                               /* Allow ascend_parse_filter's strerror to bubble up */
+                               return -1;
+                       }
                }
-               ret = sizeof(out->filter);
+
+               ret = sizeof(dst->filter);
                goto finish;
 #else
                /*
@@ -587,35 +659,14 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
 
        /* don't use this! */
        case PW_TYPE_TLV:
-       {
-               uint8_t *p;
-
-               if ((len < 2) || (len & 0x01) || (strncasecmp(value, "0x", 2) != 0)) {
-                       fr_strerror_printf("Invalid TLV specification");
-                       return -1;
-               }
-               len -= 2;
-
-               ret = len >> 1;
-               p = talloc_array(ctx, uint8_t, ret);
-               if (!p) {
-                       fr_strerror_printf("No memory");
-                       return -1;
-               }
-               if (fr_hex2bin(p, ret, value + 2, len) != (size_t)ret) {
-                       fr_strerror_printf("Invalid hex data in TLV");
-                       return -1;
-               }
-
-               out->tlv = p;
-       }
-               goto finish;
+               fr_strerror_printf("Cannot parse TLV");
+               return -1;
 
        case PW_TYPE_IPV4_ADDR:
        {
                fr_ipaddr_t addr;
 
-               if (fr_pton4(&addr, value, inlen, fr_hostname_lookups, false) < 0) return -1;
+               if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
                /*
                 *      We allow v4 addresses to have a /32 suffix as some databases (PostgreSQL)
@@ -627,7 +678,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                        return -1;
                }
 
-               out->ipaddr.s_addr = addr.ipaddr.ip4addr.s_addr;
+               dst->ipaddr.s_addr = addr.ipaddr.ip4addr.s_addr;
        }
                goto finish;
 
@@ -635,10 +686,10 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
        {
                fr_ipaddr_t addr;
 
-               if (fr_pton4(&addr, value, inlen, fr_hostname_lookups, false) < 0) return -1;
+               if (fr_pton4(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
-               out->ipv4prefix[1] = addr.prefix;
-               memcpy(out->ipv4prefix + 2, &addr.ipaddr.ip4addr.s_addr, sizeof(out->ipv4prefix) - 2);
+               dst->ipv4prefix[1] = addr.prefix;
+               memcpy(&dst->ipv4prefix[2], &addr.ipaddr.ip4addr.s_addr, sizeof(dst->ipv4prefix) - 2);
        }
                goto finish;
 
@@ -646,7 +697,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
        {
                fr_ipaddr_t addr;
 
-               if (fr_pton6(&addr, value, inlen, fr_hostname_lookups, false) < 0) return -1;
+               if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
                /*
                 *      We allow v6 addresses to have a /128 suffix as some databases (PostgreSQL)
@@ -658,7 +709,7 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                        return -1;
                }
 
-               memcpy(&out->ipv6addr, &addr.ipaddr.ip6addr.s6_addr, sizeof(out->ipv6addr));
+               memcpy(&dst->ipv6addr, addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6addr));
        }
                goto finish;
 
@@ -666,10 +717,10 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
        {
                fr_ipaddr_t addr;
 
-               if (fr_pton6(&addr, value, inlen, fr_hostname_lookups, false) < 0) return -1;
+               if (fr_pton6(&addr, src, src_len, fr_hostname_lookups, false) < 0) return -1;
 
-               out->ipv6prefix[1] = addr.prefix;
-               memcpy(out->ipv6prefix + 2, &addr.ipaddr.ip6addr.s6_addr, sizeof(out->ipv6prefix) - 2);
+               dst->ipv6prefix[1] = addr.prefix;
+               memcpy(&dst->ipv6prefix[2], addr.ipaddr.ip6addr.s6_addr, sizeof(dst->ipv6prefix) - 2);
        }
                goto finish;
 
@@ -678,21 +729,21 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
        }
 
        /*
-        *      It's a fixed size type, copy to a temporary buffer and
+        *      It's a fixed size src_type, copy to a temporary buffer and
         *      \0 terminate if insize >= 0.
         */
-       if (inlen > 0) {
+       if (src_len > 0) {
                if (len >= sizeof(buffer)) {
                        fr_strerror_printf("Temporary buffer too small");
                        return -1;
                }
 
-               memcpy(buffer, value, inlen);
-               buffer[inlen] = '\0';
-               value = buffer;
+               memcpy(buffer, src, src_len);
+               buffer[src_len] = '\0';
+               src = buffer;
        }
 
-       switch(*type) {
+       switch (*src_type) {
        case PW_TYPE_BYTE:
        {
                char *p;
@@ -701,26 +752,27 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                /*
                 *      Note that ALL integers are unsigned!
                 */
-               i = fr_strtoul(value, &p);
+               i = fr_strtoul(src, &p);
 
                /*
-                *      Look for the named value for the given
+                *      Look for the named src for the given
                 *      attribute.
                 */
-               if (enumv && *p && !is_whitespace(p)) {
-                       if ((dval = dict_valbyname(enumv->attr, enumv->vendor, value)) == NULL) {
-                               fr_strerror_printf("Unknown value '%s' for attribute '%s'", value, enumv->name);
+               if (src_enumv && *p && !is_whitespace(p)) {
+                       if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
+                                                  src, src_enumv->name);
                                return -1;
                        }
 
-                       out->byte = dval->value;
+                       dst->byte = dval->value;
                } else {
                        if (i > 255) {
-                               fr_strerror_printf("Byte value \"%s\" is larger than 255", value);
+                               fr_strerror_printf("Byte value \"%s\" is larger than 255", src);
                                return -1;
                        }
 
-                       out->byte = i;
+                       dst->byte = i;
                }
                break;
        }
@@ -733,26 +785,27 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                /*
                 *      Note that ALL integers are unsigned!
                 */
-               i = fr_strtoul(value, &p);
+               i = fr_strtoul(src, &p);
 
                /*
-                *      Look for the named value for the given
+                *      Look for the named src for the given
                 *      attribute.
                 */
-               if (enumv && *p && !is_whitespace(p)) {
-                       if ((dval = dict_valbyname(enumv->attr, enumv->vendor, value)) == NULL) {
-                               fr_strerror_printf("Unknown value '%s' for attribute '%s'", value, enumv->name);
+               if (src_enumv && *p && !is_whitespace(p)) {
+                       if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
+                                                  src, src_enumv->name);
                                return -1;
                        }
 
-                       out->ushort = dval->value;
+                       dst->ushort = dval->value;
                } else {
                        if (i > 65535) {
-                               fr_strerror_printf("Short value \"%s\" is larger than 65535", value);
+                               fr_strerror_printf("Short value \"%s\" is larger than 65535", src);
                                return -1;
                        }
 
-                       out->ushort = i;
+                       dst->ushort = i;
                }
                break;
        }
@@ -765,24 +818,25 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                /*
                 *      Note that ALL integers are unsigned!
                 */
-               i = fr_strtoul(value, &p);
+               i = fr_strtoul(src, &p);
 
                /*
-                *      Look for the named value for the given
+                *      Look for the named src for the given
                 *      attribute.
                 */
-               if (enumv && *p && !is_whitespace(p)) {
-                       if ((dval = dict_valbyname(enumv->attr, enumv->vendor, value)) == NULL) {
-                               fr_strerror_printf("Unknown value '%s' for attribute '%s'", value, enumv->name);
+               if (src_enumv && *p && !is_whitespace(p)) {
+                       if ((dval = dict_valbyname(src_enumv->attr, src_enumv->vendor, src)) == NULL) {
+                               fr_strerror_printf("Unknown or invalid value \"%s\" for attribute %s",
+                                                  src, src_enumv->name);
                                return -1;
                        }
 
-                       out->integer = dval->value;
+                       dst->integer = dval->value;
                } else {
                        /*
                         *      Value is always within the limits
                         */
-                       out->integer = i;
+                       dst->integer = i;
                }
        }
                break;
@@ -794,12 +848,11 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                /*
                 *      Note that ALL integers are unsigned!
                 */
-               if (sscanf(value, "%" PRIu64, &i) != 1) {
-                       fr_strerror_printf("Invalid value '%s' for attribute '%s'",
-                                          value, enumv->name);
+               if (sscanf(src, "%" PRIu64, &i) != 1) {
+                       fr_strerror_printf("Failed parsing \"%s\" as unsigned 64bit integer", src);
                        return -1;
                }
-               out->integer64 = i;
+               dst->integer64 = i;
        }
                break;
 
@@ -811,19 +864,19 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                 */
                time_t date;
 
-               if (fr_get_time(value, &date) < 0) {
-                       fr_strerror_printf("failed to parse time string \"%s\"", value);
+               if (fr_get_time(src, &date) < 0) {
+                       fr_strerror_printf("failed to parse time string \"%s\"", src);
                        return -1;
                }
 
-               out->date = date;
+               dst->date = date;
        }
 
                break;
 
        case PW_TYPE_IFID:
-               if (ifid_aton(value, (void *) &out->ifid) == NULL) {
-                       fr_strerror_printf("Failed to parse interface-id string \"%s\"", value);
+               if (ifid_aton(src, (void *) dst->ifid) == NULL) {
+                       fr_strerror_printf("Failed to parse interface-id string \"%s\"", src);
                        return -1;
                }
                break;
@@ -839,14 +892,14 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                 *      We assume the number is the bigendian representation of the
                 *      ethernet address.
                 */
-               if (is_integer(value)) {
-                       uint64_t integer = htonll(atoll(value));
+               if (is_integer(src)) {
+                       uint64_t integer = htonll(atoll(src));
 
-                       memcpy(&out->ether, &integer, sizeof(out->ether));
+                       memcpy(dst->ether, &integer, sizeof(dst->ether));
                        break;
                }
 
-               cp = value;
+               cp = src;
                while (*cp) {
                        if (cp[1] == ':') {
                                c1 = hextab;
@@ -860,40 +913,40 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
                        } else {
                                c1 = c2 = NULL;
                        }
-                       if (!c1 || !c2 || (p_len >= sizeof(out->ether))) {
-                               fr_strerror_printf("failed to parse Ethernet address \"%s\"", value);
+                       if (!c1 || !c2 || (p_len >= sizeof(dst->ether))) {
+                               fr_strerror_printf("failed to parse Ethernet address \"%s\"", src);
                                return -1;
                        }
-                       out->ether[p_len] = ((c1-hextab)<<4) + (c2-hextab);
+                       dst->ether[p_len] = ((c1-hextab)<<4) + (c2-hextab);
                        p_len++;
                }
        }
                break;
 
        /*
-        *      Crazy polymorphic (IPv4/IPv6) attribute type for WiMAX.
+        *      Crazy polymorphic (IPv4/IPv6) attribute src_type for WiMAX.
         *
         *      We try and make is saner by replacing the original
-        *      da, with either an IPv4 or IPv6 da type.
+        *      da, with either an IPv4 or IPv6 da src_type.
         *
         *      These are not dynamic da, and will have the same vendor
         *      and attribute as the original.
         */
        case PW_TYPE_COMBO_IP_ADDR:
        {
-               if (inet_pton(AF_INET6, value, &out->ipv6addr) > 0) {
-                       *type = PW_TYPE_IPV6_ADDR;
+               if (inet_pton(AF_INET6, src, &dst->ipv6addr) > 0) {
+                       *src_type = PW_TYPE_IPV6_ADDR;
                        ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][1]; /* size of IPv6 address */
                } else {
                        fr_ipaddr_t ipaddr;
 
-                       if (ip_hton(&ipaddr, AF_INET, value, false) < 0) {
-                               fr_strerror_printf("Failed to find IPv4 address for %s", value);
+                       if (ip_hton(&ipaddr, AF_INET, src, false) < 0) {
+                               fr_strerror_printf("Failed to find IPv4 address for %s", src);
                                return -1;
                        }
 
-                       *type = PW_TYPE_IPV4_ADDR;
-                       out->ipaddr.s_addr = ipaddr.ipaddr.ip4addr.s_addr;
+                       *src_type = PW_TYPE_IPV4_ADDR;
+                       dst->ipaddr.s_addr = ipaddr.ipaddr.ip4addr.s_addr;
                        ret = dict_attr_sizes[PW_TYPE_COMBO_IP_ADDR][0]; /* size of IPv4 address */
                }
        }
@@ -901,14 +954,14 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *out,
 
        case PW_TYPE_SIGNED:
                /* Damned code for 1 WiMAX attribute */
-               out->sinteger = (int32_t)strtol(value, NULL, 10);
+               dst->sinteger = (int32_t)strtol(src, NULL, 10);
                break;
 
-               /*
-                *  Anything else.
-                */
+       /*
+        *  Anything else.
+        */
        default:
-               fr_strerror_printf("Unknown attribute type %d", *type);
+               fr_strerror_printf("Unknown attribute type %d", *src_type);
                return -1;
        }
 
@@ -916,3 +969,913 @@ finish:
        return ret;
 }
 
+/** Performs byte order reversal for types that need it
+ *
+ */
+static void value_data_hton(value_data_t *dst, PW_TYPE type, void const *src, size_t src_len)
+{
+       /* 8 byte integers */
+       switch (type) {
+       case PW_TYPE_INTEGER64:
+               dst->integer64 = htonll(*(uint64_t const *)src);
+               break;
+
+       /* 4 byte integers */
+       case PW_TYPE_INTEGER:
+       case PW_TYPE_DATE:
+       case PW_TYPE_SIGNED:
+               dst->integer = htonl(*(uint32_t const *)src);
+               break;
+
+       /* 2 byte integers */
+       case PW_TYPE_SHORT:
+               dst->ushort = htons(*(uint16_t const *)src);
+               break;
+
+       case PW_TYPE_OCTETS:
+       case PW_TYPE_STRING:
+               fr_assert(0);
+               return;         /* shouldn't happen */
+
+       default:
+               memcpy(dst, src, src_len);
+       }
+}
+
+/** Convert one type of value_data_t to another
+ *
+ * @note This should be the canonical function used to convert between data types.
+ *
+ * @param ctx to allocate buffers in (usually the same as dst)
+ * @param dst Where to write result of casting.
+ * @param dst_type to cast to.
+ * @param dst_enumv Enumerated values used to converts strings to integers.
+ * @param src_type to cast from.
+ * @param src_enumv Enumerated values used to convert integers to strings.
+ * @param src Input data.
+ * @param src_len Input data len.
+ * @return the length of data in the dst or -1 on error.
+ */
+ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst,
+                       PW_TYPE dst_type, DICT_ATTR const *dst_enumv,
+                       PW_TYPE src_type, DICT_ATTR const *src_enumv,
+                       value_data_t const *src, size_t src_len)
+{
+       if (!fr_assert(dst_type != src_type)) return -1;
+
+       /*
+        *      Deserialise a value_data_t
+        */
+       if (src_type == PW_TYPE_STRING) {
+               return value_data_from_str(ctx, dst, &dst_type, dst_enumv, src->strvalue, src_len, '\0');
+       }
+
+       /*
+        *      Converts the src data to octets with no processing.
+        */
+       if (dst_type == PW_TYPE_OCTETS) {
+               value_data_hton(dst, src_type, src, src_len);
+               dst->octets = talloc_memdup(ctx, dst, src_len);
+               talloc_set_type(dst->octets, uint8_t);
+               return talloc_array_length(dst->strvalue);
+       }
+
+       /*
+        *      Serialise a value_data_t
+        */
+       if (dst_type == PW_TYPE_STRING) {
+               dst->strvalue = value_data_aprints(ctx, src_type, src_enumv, src, src_len, '\0');
+               return talloc_array_length(dst->strvalue) - 1;
+       }
+
+       if ((src_type == PW_TYPE_IFID) &&
+           (dst_type == PW_TYPE_INTEGER64)) {
+               memcpy(&dst->integer64, src->ifid, sizeof(src->ifid));
+               dst->integer64 = htonll(dst->integer64);
+       fixed_length:
+               return dict_attr_sizes[dst_type][0];
+       }
+
+       if ((src_type == PW_TYPE_INTEGER64) &&
+           (dst_type == PW_TYPE_ETHERNET)) {
+               uint8_t array[8];
+               uint64_t i;
+
+               i = htonll(src->integer64);
+               memcpy(array, &i, 8);
+
+               /*
+                *      For OUIs in the DB.
+                */
+               if ((array[0] != 0) || (array[1] != 0)) return -1;
+
+               memcpy(dst->ether, &array[2], 6);
+               goto fixed_length;
+       }
+
+       /*
+        *      For integers, we allow the casting of a SMALL type to
+        *      a larger type, but not vice-versa.
+        */
+       if (dst_type == PW_TYPE_INTEGER64) {
+               switch (src_type) {
+               case PW_TYPE_BYTE:
+                       dst->integer64 = src->byte;
+                       break;
+
+               case PW_TYPE_SHORT:
+                       dst->integer64 = src->ushort;
+                       break;
+
+               case PW_TYPE_INTEGER:
+                       dst->integer64 = src->integer;
+                       break;
+
+               case PW_TYPE_DATE:
+                       dst->integer64 = src->date;
+                       break;
+
+               case PW_TYPE_OCTETS:
+                       goto do_octets;
+
+               default:
+               invalid_cast:
+                       fr_strerror_printf("Invalid cast from %s to %s",
+                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                       return -1;
+
+               }
+               goto fixed_length;
+       }
+
+       /*
+        *      We can cast LONG integers to SHORTER ones, so long
+        *      as the long one is on the LHS.
+        */
+       if (dst_type == PW_TYPE_INTEGER) {
+               switch (src_type) {
+               case PW_TYPE_BYTE:
+                       dst->integer = src->byte;
+                       break;
+
+               case PW_TYPE_SHORT:
+                       dst->integer = src->ushort;
+                       break;
+
+               case PW_TYPE_OCTETS:
+                       goto do_octets;
+
+               default:
+                       goto invalid_cast;
+               }
+               goto fixed_length;
+       }
+
+       if (dst_type == PW_TYPE_SHORT) {
+               switch (src_type) {
+               case PW_TYPE_BYTE:
+                       dst->ushort = src->byte;
+                       break;
+
+               case PW_TYPE_OCTETS:
+                       goto do_octets;
+
+               default:
+                       goto invalid_cast;
+               }
+               goto fixed_length;
+       }
+
+       /*
+        *      We can cast integers less that < INT_MAX to signed
+        */
+       if (dst_type == PW_TYPE_SIGNED) {
+               switch (src_type) {
+               case PW_TYPE_BYTE:
+                       dst->sinteger = src->byte;
+                       break;
+
+               case PW_TYPE_SHORT:
+                       dst->sinteger = src->ushort;
+                       break;
+
+               case PW_TYPE_INTEGER:
+                       if (src->integer > INT_MAX) {
+                               fr_strerror_printf("Invalid cast: From integer to signed.  integer value %u is larger "
+                                                  "than max signed int and would overflow", src->integer);
+                               return -1;
+                       }
+                       dst->sinteger = (int)src->integer;
+                       break;
+
+               case PW_TYPE_INTEGER64:
+                       if (src->integer > INT_MAX) {
+                               fr_strerror_printf("Invalid cast: From integer64 to signed.  integer64 value %" PRIu64
+                                                  " is larger than max signed int and would overflow", src->integer64);
+                               return -1;
+                       }
+                       dst->sinteger = (int)src->integer64;
+                       break;
+
+               case PW_TYPE_OCTETS:
+                       goto do_octets;
+
+               default:
+                       goto invalid_cast;
+               }
+               goto fixed_length;
+       }
+       /*
+        *      Conversions between IPv4 addresses, IPv6 addresses, IPv4 prefixes and IPv6 prefixes
+        *
+        *      For prefix to ipaddress conversions, we assume that the host portion has already
+        *      been zeroed out.
+        *
+        *      We allow casts from v6 to v4 if the v6 address has the correct mapping prefix.
+        *
+        *      We only allow casts from prefixes to addresses if the prefix is the the length of
+        *      the address, e.g. 32 for ipv4 128 for ipv6.
+        */
+       {
+               /*
+                *      10 bytes of 0x00 2 bytes of 0xff
+                */
+               static uint8_t const v4_v6_map[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                                    0x00, 0x00, 0x00, 0x00, 0xff, 0xff };
+
+               switch (dst_type) {
+               case PW_TYPE_IPV4_ADDR:
+                       switch (src_type) {
+                       case PW_TYPE_IPV6_ADDR:
+                               if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                               bad_v6_prefix_map:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  No IPv4-IPv6 mapping prefix",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+
+                               memcpy(&dst->ipaddr, &src->ipv6addr.s6_addr[sizeof(v4_v6_map)],
+                                      sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               if (src->ipv4prefix[1] != 32) {
+                               bad_v4_prefix_len:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  Only /32 prefixes may be "
+                                                          "cast to IP address types",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+
+                               memcpy(&dst->ipaddr, &src->ipv4prefix[2], sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (src->ipv6prefix[1] != 128) {
+                               bad_v6_prefix_len:
+                                       fr_strerror_printf("Invalid cast from %s to %s.  Only /128 prefixes may be "
+                                                          "cast to IP address types",
+                                                          fr_int2str(dict_attr_types, src_type, "<INVALID>"),
+                                                          fr_int2str(dict_attr_types, dst_type, "<INVALID>"));
+                                       return -1;
+                               }
+                               if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+                               memcpy(&dst->ipaddr, &src->ipv6prefix[2 + sizeof(v4_v6_map)],
+                                      sizeof(dst->ipaddr));
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV6_ADDR:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipaddr,
+                                      sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map));
+
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               if (src->ipv4prefix[1] != 32) goto bad_v4_prefix_len;
+
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(dst->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6addr.s6_addr[sizeof(v4_v6_map)], &src->ipv4prefix[2],
+                                      sizeof(dst->ipv6addr.s6_addr) - sizeof(v4_v6_map));
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (src->ipv4prefix[1] != 128) goto bad_v6_prefix_len;
+
+                               memcpy(dst->ipv6addr.s6_addr, &src->ipv6prefix[2], sizeof(dst->ipv6addr.s6_addr));
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV4_PREFIX:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               memcpy(&dst->ipv4prefix[2], &src->ipaddr, sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = 32;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_ADDR:
+                               if (memcmp(src->ipv6addr.s6_addr, v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+                               memcpy(&dst->ipv4prefix[2], &src->ipv6addr.s6_addr[sizeof(v4_v6_map)],
+                                      sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = 32;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_PREFIX:
+                               if (memcmp(&src->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map)) != 0) {
+                                       goto bad_v6_prefix_map;
+                               }
+
+                               /*
+                                *      Prefix must be >= 96 bits. If it's < 96 bytes and the
+                                *      above check passed, the v6 address wasn't masked
+                                *      correctly when it was packet into a value_data_t.
+                                */
+                               if (!fr_assert(src->ipv6prefix[1] >= (sizeof(v4_v6_map) * 8))) return -1;
+
+                               memcpy(&dst->ipv4prefix[2], &src->ipv6prefix[2 + sizeof(v4_v6_map)],
+                                      sizeof(dst->ipv4prefix) - 2);
+                               dst->ipv4prefix[0] = 0;
+                               dst->ipv4prefix[1] = src->ipv6prefix[1] - (sizeof(v4_v6_map) * 8);
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+                       break;
+
+               case PW_TYPE_IPV6_PREFIX:
+                       switch (src_type) {
+                       case PW_TYPE_IPV4_ADDR:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipaddr,
+                                      (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map));
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = 128;
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV4_PREFIX:
+                               /* Add the v4/v6 mapping prefix */
+                               memcpy(&dst->ipv6prefix[2], v4_v6_map, sizeof(v4_v6_map));
+                               memcpy(&dst->ipv6prefix[2 + sizeof(v4_v6_map)], &src->ipv4prefix[2],
+                                      (sizeof(dst->ipv6prefix) - 2) - sizeof(v4_v6_map));
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = (sizeof(v4_v6_map) * 8) + src->ipv4prefix[1];
+                               goto fixed_length;
+
+                       case PW_TYPE_IPV6_ADDR:
+                               memcpy(&dst->ipv6prefix[2], &src->ipv6addr, sizeof(dst->ipv6prefix) - 2);
+                               dst->ipv6prefix[0] = 0;
+                               dst->ipv6prefix[1] = 128;
+                               goto fixed_length;
+
+                       default:
+                               break;
+                       }
+
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       /*
+        *      The attribute we've found has to have a size which is
+        *      compatible with the type of the destination cast.
+        */
+       if ((src_len < dict_attr_sizes[dst_type][0]) ||
+           (src_len > dict_attr_sizes[dst_type][1])) {
+               char const *src_type_name;
+
+               src_type_name =  fr_int2str(dict_attr_types, src_type, "<INVALID>");
+               fr_strerror_printf("Invalid cast from %s to %s. Length should be between %zu and %zu but is %zu",
+                                  src_type_name,
+                                  fr_int2str(dict_attr_types, dst_type, "<INVALID>"),
+                                  dict_attr_sizes[dst_type][0], dict_attr_sizes[dst_type][1],
+                                  src_len);
+               return -1;
+       }
+
+       if (src_type == PW_TYPE_OCTETS) {
+       do_octets:
+               value_data_hton(dst, dst_type, src->octets, src_len);
+               return src_len;
+       }
+
+       /*
+        *      Convert host order to network byte order.
+        */
+       if ((dst_type == PW_TYPE_IPV4_ADDR) &&
+           ((src_type == PW_TYPE_INTEGER) ||
+            (src_type == PW_TYPE_DATE) ||
+            (src_type == PW_TYPE_SIGNED))) {
+               dst->ipaddr.s_addr = htonl(src->integer);
+
+       } else if ((src_type == PW_TYPE_IPV4_ADDR) &&
+                  ((dst_type == PW_TYPE_INTEGER) ||
+                   (dst_type == PW_TYPE_DATE) ||
+                   (dst_type == PW_TYPE_SIGNED))) {
+               dst->integer = htonl(src->ipaddr.s_addr);
+
+       } else {                /* they're of the same byte order */
+               memcpy(&dst, &src, src_len);
+       }
+
+       return src_len;
+}
+
+/** Copy value data verbatim duplicating any buffers
+ *
+ * @param ctx To allocate buffers in.
+ * @param dst Where to copy value_data to.
+ * @param src_type Type of src.
+ * @param src Where to copy value_data from.
+ * @param src_len Where
+ */
+ssize_t value_data_copy(TALLOC_CTX *ctx, value_data_t *dst, PW_TYPE src_type,
+                       const value_data_t *src, size_t src_len)
+{
+       switch (src_type) {
+       default:
+               memcpy(dst, src, sizeof(*src));
+               break;
+
+       case PW_TYPE_STRING:
+               dst->strvalue = talloc_bstrndup(ctx, src->strvalue, src_len);
+               if (!dst->strvalue) return -1;
+               break;
+
+       case PW_TYPE_OCTETS:
+               dst->octets = talloc_memdup(ctx, src->octets, src_len);
+               talloc_set_type(dst->strvalue, uint8_t);
+               if (!dst->octets) return -1;
+               break;
+       }
+
+       return src_len;
+}
+
+
+
+/** Print one attribute value to a string
+ *
+ */
+char *value_data_aprints(TALLOC_CTX *ctx,
+                        PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data,
+                        size_t inlen, char quote)
+{
+       char *p = NULL;
+       unsigned int i;
+
+       switch (type) {
+       case PW_TYPE_STRING:
+       {
+               size_t len, ret;
+
+               if (!quote) {
+                       p = talloc_bstrndup(ctx, data->strvalue, inlen);
+                       if (!p) return NULL;
+                       talloc_set_type(p, char);
+                       return p;
+               }
+
+               /* Gets us the size of the buffer we need to alloc */
+               len = fr_prints_len(data->strvalue, inlen, quote);
+               p = talloc_array(ctx, char, len);
+               if (!p) return NULL;
+
+               ret = fr_prints(p, len, data->strvalue, inlen, quote);
+               if (!fr_assert(ret == (len - 1))) {
+                       talloc_free(p);
+                       return NULL;
+               }
+               break;
+       }
+
+       case PW_TYPE_INTEGER:
+               i = data->integer;
+               goto print_int;
+
+       case PW_TYPE_SHORT:
+               i = data->ushort;
+               goto print_int;
+
+       case PW_TYPE_BYTE:
+               i = data->byte;
+
+       print_int:
+       {
+               DICT_VALUE const *dv;
+
+               if (enumv && (dv = dict_valbyattr(enumv->attr, enumv->vendor, i))) {
+                       p = talloc_typed_strdup(ctx, dv->name);
+               } else {
+                       p = talloc_typed_asprintf(ctx, "%u", i);
+               }
+       }
+               break;
+
+       case PW_TYPE_SIGNED:
+               p = talloc_typed_asprintf(ctx, "%d", data->sinteger);
+               break;
+
+       case PW_TYPE_INTEGER64:
+               p = talloc_typed_asprintf(ctx, "%" PRIu64 , data->integer64);
+               break;
+
+       case PW_TYPE_ETHERNET:
+               p = talloc_typed_asprintf(ctx, "%02x:%02x:%02x:%02x:%02x:%02x",
+                                         data->ether[0], data->ether[1],
+                                         data->ether[2], data->ether[3],
+                                         data->ether[4], data->ether[5]);
+               break;
+
+       case PW_TYPE_ABINARY:
+#ifdef WITH_ASCEND_BINARY
+               p = talloc_array(ctx, char, 128);
+               if (!p) return NULL;
+               print_abinary(p, 128, (uint8_t const *) &data->filter, inlen, 0);
+               break;
+#else
+                 /* FALL THROUGH */
+#endif
+
+       case PW_TYPE_OCTETS:
+               p = talloc_array(ctx, char, 2 + 1 + inlen * 2);
+               if (!p) return NULL;
+               p[0] = '0';
+               p[1] = 'x';
+
+               fr_bin2hex(p + 2, data->octets, inlen);
+               p[2 + (inlen * 2)] = '\0';
+               break;
+
+       case PW_TYPE_DATE:
+       {
+               time_t t;
+               struct tm s_tm;
+
+               t = data->date;
+
+               p = talloc_array(ctx, char, 64);
+               strftime(p, 64, "%b %e %Y %H:%M:%S %Z",
+                        localtime_r(&t, &s_tm));
+               break;
+       }
+
+       /*
+        *      We need to use the proper inet_ntop functions for IP
+        *      addresses, else the output might not match output of
+        *      other functions, which makes testing difficult.
+        *
+        *      An example is tunnelled ipv4 in ipv6 addresses.
+        */
+       case PW_TYPE_IPV4_ADDR:
+       case PW_TYPE_IPV4_PREFIX:
+       {
+               char buff[INET_ADDRSTRLEN  + 4]; // + /prefix
+
+               buff[0] = '\0';
+               value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0');
+
+               p = talloc_typed_strdup(ctx, buff);
+       }
+       break;
+
+       case PW_TYPE_IPV6_ADDR:
+       case PW_TYPE_IPV6_PREFIX:
+       {
+               char buff[INET6_ADDRSTRLEN + 4]; // + /prefix
+
+               buff[0] = '\0';
+               value_data_prints(buff, sizeof(buff), type, enumv, data, inlen, '\0');
+
+               p = talloc_typed_strdup(ctx, buff);
+       }
+       break;
+
+       case PW_TYPE_IFID:
+               p = talloc_typed_asprintf(ctx, "%x:%x:%x:%x",
+                                         (data->ifid[0] << 8) | data->ifid[1],
+                                         (data->ifid[2] << 8) | data->ifid[3],
+                                         (data->ifid[4] << 8) | data->ifid[5],
+                                         (data->ifid[6] << 8) | data->ifid[7]);
+               break;
+
+       case PW_TYPE_BOOLEAN:
+               p = talloc_typed_strdup(ctx, data->byte ? "yes" : "no");
+               break;
+
+       /*
+        *      Don't add default here
+        */
+       case PW_TYPE_INVALID:
+       case PW_TYPE_COMBO_IP_ADDR:
+       case PW_TYPE_COMBO_IP_PREFIX:
+       case PW_TYPE_TLV:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+       case PW_TYPE_EVS:
+       case PW_TYPE_VSA:
+       case PW_TYPE_TIMEVAL:
+       case PW_TYPE_MAX:
+               fr_assert(0);
+               return NULL;
+       }
+
+       return p;
+}
+
+
+/** Print the value of an attribute to a string
+ *
+ * @note return value should be checked with is_truncated.
+ * @note Will always \0 terminate unless outlen == 0.
+ *
+ * @param out Where to write the printed version of the attribute value.
+ * @param outlen Length of the output buffer.
+ * @param type of data being printed.
+ * @param enumv Enumerated string values for integer types.
+ * @param data to print.
+ * @param inlen Length of data.
+ * @param quote char to escape in string output.
+ * @return  the number of bytes written to the out buffer, or a number >= outlen if truncation has occurred.
+ */
+size_t value_data_prints(char *out, size_t outlen,
+                        PW_TYPE type, DICT_ATTR const *enumv, value_data_t const *data,
+                        ssize_t inlen, char quote)
+{
+       DICT_VALUE      *v;
+       char            buf[1024];      /* Interim buffer to use with poorly behaved printing functions */
+       char const      *a = NULL;
+       char            *p = out;
+       time_t          t;
+       struct tm       s_tm;
+       unsigned int    i;
+
+       size_t          len = 0, freespace = outlen;
+
+       if (!data) return 0;
+       if (outlen == 0) return inlen;
+
+       *out = '\0';
+
+       p = out;
+
+       switch (type) {
+       case PW_TYPE_STRING:
+
+               /*
+                *      Ensure that WE add the quotation marks around the string.
+                */
+               if (quote) {
+                       if (freespace < 3) return inlen + 2;
+
+                       *p++ = quote;
+                       freespace--;
+
+                       len = fr_prints(p, freespace, data->strvalue, inlen, quote);
+                       /* always terminate the quoted string with another quote */
+                       if (len >= (freespace - 1)) {
+                               /* Use out not p as we're operating on the entire buffer */
+                               out[outlen - 2] = (char) quote;
+                               out[outlen - 1] = '\0';
+                               return len + 2;
+                       }
+                       p += len;
+                       freespace -= len;
+
+                       *p++ = (char) quote;
+                       freespace--;
+                       *p = '\0';
+
+                       return len + 2;
+               }
+
+               return fr_prints(out, outlen, data->strvalue, inlen, quote);
+
+       case PW_TYPE_INTEGER:
+               i = data->integer;
+               goto print_int;
+
+       case PW_TYPE_SHORT:
+               i = data->ushort;
+               goto print_int;
+
+       case PW_TYPE_BYTE:
+               i = data->byte;
+
+print_int:
+               /* Normal, non-tagged attribute */
+               if (enumv && (v = dict_valbyattr(enumv->attr, enumv->vendor, i)) != NULL) {
+                       a = v->name;
+                       len = strlen(a);
+               } else {
+                       /* should never be truncated */
+                       len = snprintf(buf, sizeof(buf), "%u", i);
+                       a = buf;
+               }
+               break;
+
+       case PW_TYPE_INTEGER64:
+               return snprintf(out, outlen, "%" PRIu64, data->integer64);
+
+       case PW_TYPE_DATE:
+               t = data->date;
+               if (quote > 0) {
+                       len = strftime(buf, sizeof(buf) - 1, "%%%b %e %Y %H:%M:%S %Z%%", localtime_r(&t, &s_tm));
+                       buf[0] = (char) quote;
+                       buf[len - 1] = (char) quote;
+                       buf[len] = '\0';
+               } else {
+                       len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S %Z", localtime_r(&t, &s_tm));
+               }
+               a = buf;
+               break;
+
+       case PW_TYPE_SIGNED: /* Damned code for 1 WiMAX attribute */
+               len = snprintf(buf, sizeof(buf), "%d", data->sinteger);
+               a = buf;
+               break;
+
+       case PW_TYPE_IPV4_ADDR:
+               a = inet_ntop(AF_INET, &(data->ipaddr), buf, sizeof(buf));
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_ABINARY:
+#ifdef WITH_ASCEND_BINARY
+               print_abinary(buf, sizeof(buf), (uint8_t const *) data->filter, inlen, quote);
+               a = buf;
+               len = strlen(buf);
+               break;
+#else
+       /* FALL THROUGH */
+#endif
+       case PW_TYPE_OCTETS:
+       case PW_TYPE_TLV:
+       {
+               size_t binlen;
+               size_t hexlen;
+
+               binlen = inlen;
+               hexlen = (binlen * 2) + 2; /* NOT accounting for trailing NUL */
+
+               /*
+                *      If the buffer is too small, put something into
+                *      it, and return how much we should have written
+                *
+                *      0 + x + H + H + NUL = 5
+                */
+               if (freespace < 5) {
+                       switch (freespace) {
+                       case '4':
+                       case '3':
+                               out[0] = '0';
+                               out[1] = 'x';
+                               out[2] = '\0';
+                               return hexlen;
+
+                       case 2:
+                               *out = '0';
+                               out++;
+                               /* FALL-THROUGH */
+
+                       case 1:
+                               *out = '\0';
+                               break;
+
+                       case 0:
+                               break;
+                       }
+
+                       return hexlen;
+               }
+
+               /*
+                *      The output buffer is at least 5 bytes, we haev
+                *      room for '0xHH' plus a trailing NUL byte.
+                */
+               out[0] = '0';
+               out[1] = 'x';
+
+               /*
+                *      Get maximum number of bytes we can encode
+                *      given freespace, ensuring we account for '0',
+                *      'x', and the trailing NUL in the buffer.
+                *
+                *      Note that we can't have "freespace = 0" after
+                *      this, as 'freespace' has to be at least 5.
+                */
+               freespace -= 3;
+               freespace /= 2;
+               if (binlen > freespace) {
+                       binlen = freespace;
+               }
+
+               fr_bin2hex(out + 2, data->octets, binlen);
+               return hexlen;
+       }
+
+       case PW_TYPE_IFID:
+               a = ifid_ntoa(buf, sizeof(buf), data->ifid);
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_IPV6_ADDR:
+               a = inet_ntop(AF_INET6, &data->ipv6addr, buf, sizeof(buf));
+               len = strlen(buf);
+               break;
+
+       case PW_TYPE_IPV6_PREFIX:
+       {
+               struct in6_addr addr;
+
+               /*
+                *      Alignment issues.
+                */
+               memcpy(&addr, &(data->ipv6prefix[2]), sizeof(addr));
+
+               a = inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
+               if (a) {
+                       p = buf;
+
+                       len = strlen(buf);
+                       p += len;
+                       len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) data->ipv6prefix[1]);
+               }
+       }
+               break;
+
+       case PW_TYPE_IPV4_PREFIX:
+       {
+               struct in_addr addr;
+
+               /*
+                *      Alignment issues.
+                */
+               memcpy(&addr, &(data->ipv4prefix[2]), sizeof(addr));
+
+               a = inet_ntop(AF_INET, &addr, buf, sizeof(buf));
+               if (a) {
+                       p = buf;
+
+                       len = strlen(buf);
+                       p += len;
+                       len += snprintf(p, sizeof(buf) - len, "/%u", (unsigned int) (data->ipv4prefix[1] & 0x3f));
+               }
+       }
+               break;
+
+       case PW_TYPE_ETHERNET:
+               return snprintf(out, outlen, "%02x:%02x:%02x:%02x:%02x:%02x",
+                               data->ether[0], data->ether[1],
+                               data->ether[2], data->ether[3],
+                               data->ether[4], data->ether[5]);
+
+       /*
+        *      Don't add default here
+        */
+       case PW_TYPE_INVALID:
+       case PW_TYPE_COMBO_IP_ADDR:
+       case PW_TYPE_COMBO_IP_PREFIX:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+       case PW_TYPE_EVS:
+       case PW_TYPE_VSA:
+       case PW_TYPE_TIMEVAL:
+       case PW_TYPE_BOOLEAN:
+       case PW_TYPE_MAX:
+               fr_assert(0);
+               *out = '\0';
+               return 0;
+       }
+
+       if (a) strlcpy(out, a, outlen);
+
+       return len;     /* Return the number of bytes we would of written (for truncation detection) */
+}
+