root/sys/dev/bhnd/nvram/bhnd_nvram_value_fmts.c
/*-
 * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <sys/param.h>

#include <net/ethernet.h>

#ifdef _KERNEL

#include <sys/ctype.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <machine/_inttypes.h>

#else /* !_KERNEL */

#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#endif /* _KERNEL */

#include "bhnd_nvram_private.h"

#include "bhnd_nvram_valuevar.h"

static bool              bhnd_nvram_ident_octet_string(const char *inp,
                             size_t ilen, char *delim, size_t *nelem);
static bool              bhnd_nvram_ident_num_string(const char *inp,
                             size_t ilen, u_int base, u_int *obase);

static int               bhnd_nvram_val_bcm_macaddr_filter(
                             const bhnd_nvram_val_fmt **fmt, const void *inp,
                             size_t ilen, bhnd_nvram_type itype);
static int               bhnd_nvram_val_bcm_macaddr_encode(
                             bhnd_nvram_val *value, void *outp, size_t *olen,
                             bhnd_nvram_type otype);

static int               bhnd_nvram_val_bcm_macaddr_string_filter(
                             const bhnd_nvram_val_fmt **fmt, const void *inp,
                             size_t ilen, bhnd_nvram_type itype);
static int               bhnd_nvram_val_bcm_macaddr_string_encode_elem(
                             bhnd_nvram_val *value, const void *inp,
                             size_t ilen, void *outp, size_t *olen, 
                             bhnd_nvram_type otype);
static const void       *bhnd_nvram_val_bcm_macaddr_string_next(
                             bhnd_nvram_val *value, const void *prev,
                             size_t *len);

static int               bhnd_nvram_val_bcm_int_filter(
                             const bhnd_nvram_val_fmt **fmt, const void *inp,
                             size_t ilen, bhnd_nvram_type itype);
static int               bhnd_nvram_val_bcm_int_encode(bhnd_nvram_val *value,
                             void *outp, size_t *olen, bhnd_nvram_type otype);

static int               bhnd_nvram_val_bcm_decimal_encode_elem(
                             bhnd_nvram_val *value, const void *inp,
                             size_t ilen, void *outp, size_t *olen,
                             bhnd_nvram_type otype);
static int               bhnd_nvram_val_bcm_hex_encode_elem(
                             bhnd_nvram_val *value, const void *inp,
                             size_t ilen, void *outp, size_t *olen,
                             bhnd_nvram_type otype);

static int               bhnd_nvram_val_bcm_leddc_filter(
                             const bhnd_nvram_val_fmt **fmt, const void *inp,
                             size_t ilen, bhnd_nvram_type itype);
static int               bhnd_nvram_val_bcm_leddc_encode_elem(
                             bhnd_nvram_val *value, const void *inp,
                             size_t ilen, void *outp, size_t *olen,
                             bhnd_nvram_type otype);

static int               bhnd_nvram_val_bcmstr_encode(bhnd_nvram_val *value,
                             void *outp, size_t *olen, bhnd_nvram_type otype);

static int               bhnd_nvram_val_bcmstr_csv_filter(
                             const bhnd_nvram_val_fmt **fmt, const void *inp,
                             size_t ilen, bhnd_nvram_type itype);
static const void       *bhnd_nvram_val_bcmstr_csv_next(bhnd_nvram_val *value,
                             const void *prev, size_t *len);

/**
 * Broadcom NVRAM MAC address format.
 */
const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_macaddr_fmt = {
        .name           = "bcm-macaddr",
        .native_type    = BHND_NVRAM_TYPE_UINT8_ARRAY,
        .op_filter      = bhnd_nvram_val_bcm_macaddr_filter,
        .op_encode      = bhnd_nvram_val_bcm_macaddr_encode,
};

/** Broadcom NVRAM MAC address string format. */
static const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_macaddr_string_fmt = {
        .name           = "bcm-macaddr-string",
        .native_type    = BHND_NVRAM_TYPE_STRING,
        .op_filter      = bhnd_nvram_val_bcm_macaddr_string_filter,
        .op_encode_elem = bhnd_nvram_val_bcm_macaddr_string_encode_elem,
        .op_next        = bhnd_nvram_val_bcm_macaddr_string_next,
};

/**
 * Broadcom NVRAM LED duty-cycle format.
 */
const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_leddc_fmt = {
        .name           = "bcm-leddc",
        .native_type    = BHND_NVRAM_TYPE_UINT32,
        .op_filter      = bhnd_nvram_val_bcm_leddc_filter,
        .op_encode_elem = bhnd_nvram_val_bcm_leddc_encode_elem,
};

/**
 * Broadcom NVRAM decimal integer format.
 *
 * Extends standard integer handling, encoding the string representation of
 * the integer value as a decimal string:
 * - Positive values will be string-encoded without a prefix.
 * - Negative values will be string-encoded with a leading '-' sign.
 */
const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_decimal_fmt = {
        .name           = "bcm-decimal",
        .native_type    = BHND_NVRAM_TYPE_UINT64,
        .op_filter      = bhnd_nvram_val_bcm_int_filter,
        .op_encode      = bhnd_nvram_val_bcm_int_encode,
        .op_encode_elem = bhnd_nvram_val_bcm_decimal_encode_elem,
};

/**
 * Broadcom NVRAM decimal integer format.
 *
 * Extends standard integer handling, encoding the string representation of
 * unsigned and positive signed integer values as an 0x-prefixed hexadecimal
 * string.
 * 
 * For compatibility with standard Broadcom NVRAM parsing, if the integer is
 * both signed and negative, it will be string encoded as a negative decimal
 * value, not as a twos-complement hexadecimal value.
 */
const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_hex_fmt = {
        .name           = "bcm-hex",
        .native_type    = BHND_NVRAM_TYPE_UINT64,
        .op_filter      = bhnd_nvram_val_bcm_int_filter,
        .op_encode      = bhnd_nvram_val_bcm_int_encode,
        .op_encode_elem = bhnd_nvram_val_bcm_hex_encode_elem,
};

/**
 * Broadcom NVRAM string format.
 * 
 * Handles standard, comma-delimited, and octet-string values as used in
 * Broadcom NVRAM data.
 */
const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_string_fmt = {
        .name           = "bcm-string",
        .native_type    = BHND_NVRAM_TYPE_STRING,
        .op_encode      = bhnd_nvram_val_bcmstr_encode,
};

/** Broadcom comma-delimited string. */
static const bhnd_nvram_val_fmt bhnd_nvram_val_bcm_string_csv_fmt = {
        .name           = "bcm-string[]",
        .native_type    = BHND_NVRAM_TYPE_STRING,
        .op_filter      = bhnd_nvram_val_bcmstr_csv_filter,
        .op_next        = bhnd_nvram_val_bcmstr_csv_next,
};

/* Built-in format definitions */
#define BHND_NVRAM_VAL_FMT_NATIVE(_n, _type)                            \
        const bhnd_nvram_val_fmt bhnd_nvram_val_ ## _n ## _fmt = {      \
                .name           = __STRING(_n),                         \
                .native_type    = BHND_NVRAM_TYPE_ ## _type,            \
        }

BHND_NVRAM_VAL_FMT_NATIVE(uint8,        UINT8);
BHND_NVRAM_VAL_FMT_NATIVE(uint16,       UINT16);
BHND_NVRAM_VAL_FMT_NATIVE(uint32,       UINT32);
BHND_NVRAM_VAL_FMT_NATIVE(uint64,       UINT64);
BHND_NVRAM_VAL_FMT_NATIVE(int8,         INT8);
BHND_NVRAM_VAL_FMT_NATIVE(int16,        INT16);
BHND_NVRAM_VAL_FMT_NATIVE(int32,        INT32);
BHND_NVRAM_VAL_FMT_NATIVE(int64,        INT64);
BHND_NVRAM_VAL_FMT_NATIVE(char,         CHAR);
BHND_NVRAM_VAL_FMT_NATIVE(bool,         BOOL);
BHND_NVRAM_VAL_FMT_NATIVE(string,       STRING);
BHND_NVRAM_VAL_FMT_NATIVE(data,         DATA);
BHND_NVRAM_VAL_FMT_NATIVE(null,         NULL);

BHND_NVRAM_VAL_FMT_NATIVE(uint8_array,  UINT8_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(uint16_array, UINT16_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(uint32_array, UINT32_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(uint64_array, UINT64_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(int8_array,   INT8_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(int16_array,  INT16_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(int32_array,  INT32_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(int64_array,  INT64_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(char_array,   CHAR_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(bool_array,   BOOL_ARRAY);
BHND_NVRAM_VAL_FMT_NATIVE(string_array, STRING_ARRAY);

/**
 * Common hex/decimal integer filter implementation.
 */
static int
bhnd_nvram_val_bcm_int_filter(const bhnd_nvram_val_fmt **fmt, const void *inp,
    size_t ilen, bhnd_nvram_type itype)
{
        bhnd_nvram_type itype_base;

        itype_base = bhnd_nvram_base_type(itype);

        switch (itype_base) {
        case BHND_NVRAM_TYPE_STRING:
                /*
                 * If the input is a string, delegate to the Broadcom
                 * string format -- preserving the original string value
                 * takes priority over enforcing hexadecimal/integer string
                 * formatting.
                 */
                *fmt = &bhnd_nvram_val_bcm_string_fmt;
                return (0);

        default:
                if (bhnd_nvram_is_int_type(itype_base))
                        return (0);

                return (EFTYPE);
        }
}

/**
 * Broadcom hex/decimal integer encode implementation.
 */
static int
bhnd_nvram_val_bcm_int_encode(bhnd_nvram_val *value, void *outp, size_t *olen,
    bhnd_nvram_type otype)
{
        /* If encoding to a string, format multiple elements (if any) with a
         * comma delimiter. */
        if (otype == BHND_NVRAM_TYPE_STRING)
                return (bhnd_nvram_val_printf(value, "%[]s", outp, olen, ","));

        return (bhnd_nvram_val_generic_encode(value, outp, olen, otype));
}

/**
 * Broadcom hex integer encode_elem implementation.
 */
static int
bhnd_nvram_val_bcm_hex_encode_elem(bhnd_nvram_val *value, const void *inp,
    size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
        bhnd_nvram_type itype;
        ssize_t         width;
        int             error;

        itype = bhnd_nvram_val_elem_type(value);
        BHND_NV_ASSERT(bhnd_nvram_is_int_type(itype), ("invalid type"));

        /* If not encoding as a string, perform generic value encoding */
        if (otype != BHND_NVRAM_TYPE_STRING)
                return (bhnd_nvram_val_generic_encode_elem(value, inp, ilen,
                    outp, olen, otype));

        /* If the value is a signed, negative value, encode as a decimal
         * string */
        if (bhnd_nvram_is_signed_type(itype)) {
                int64_t         sval;
                size_t          slen;
                bhnd_nvram_type stype;

                stype = BHND_NVRAM_TYPE_INT64;
                slen = sizeof(sval);

                /* Fetch 64-bit signed representation */
                error = bhnd_nvram_value_coerce(inp, ilen, itype, &sval, &slen,
                    stype);
                if (error)
                        return (error);

                /* Decimal encoding required? */
                if (sval < 0)
                        return (bhnd_nvram_value_printf("%I64d", &sval, slen,
                            stype, outp, olen, otype));
        }

        /*
         * Encode the value as a hex string.
         * 
         * Most producers of Broadcom NVRAM values zero-pad hex values out to
         * their native width (width * two hex characters), and we do the same
         * for compatibility
         */
        width = bhnd_nvram_type_width(itype) * 2;
        return (bhnd_nvram_value_printf("0x%0*I64X", inp, ilen, itype,
            outp, olen, width));
}

/**
 * Broadcom decimal integer encode_elem implementation.
 */
static int
bhnd_nvram_val_bcm_decimal_encode_elem(bhnd_nvram_val *value, const void *inp,
    size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
        const char      *sfmt;
        bhnd_nvram_type  itype;

        itype = bhnd_nvram_val_elem_type(value);
        BHND_NV_ASSERT(bhnd_nvram_is_int_type(itype), ("invalid type"));

        /* If not encoding as a string, perform generic value encoding */
        if (otype != BHND_NVRAM_TYPE_STRING)
                return (bhnd_nvram_val_generic_encode_elem(value, inp, ilen,
                    outp, olen, otype));

        sfmt = bhnd_nvram_is_signed_type(itype) ? "%I64d" : "%I64u";
        return (bhnd_nvram_value_printf(sfmt, inp, ilen, itype, outp, olen));
}

/**
 * Broadcom LED duty-cycle filter.
 */
static int
bhnd_nvram_val_bcm_leddc_filter(const bhnd_nvram_val_fmt **fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype)
{
        const char      *p;
        size_t           plen;

        switch (itype) {
        case BHND_NVRAM_TYPE_UINT16:
        case BHND_NVRAM_TYPE_UINT32:
                return (0);

        case BHND_NVRAM_TYPE_STRING:
                /* Trim any whitespace */
                p = inp;
                plen = bhnd_nvram_trim_field(&p, ilen, '\0');

                /* If the value is not a valid integer string, delegate to the
                 * Broadcom string format */
                if (!bhnd_nvram_ident_num_string(p, plen, 0, NULL))
                        *fmt = &bhnd_nvram_val_bcm_string_fmt;

                return (0);
        default:
                return (EFTYPE);
        }
}

/**
 * Broadcom LED duty-cycle encode.
 */
static int
bhnd_nvram_val_bcm_leddc_encode_elem(bhnd_nvram_val *value, const void *inp,
    size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
        bhnd_nvram_type         itype;
        size_t                  limit, nbytes;
        int                     error;
        uint16_t                led16;
        uint32_t                led32;
        bool                    led16_lossy;
        union {
                uint16_t        u16;
                uint32_t        u32;
        } strval;

        /*
         * LED duty-cycle values represent the on/off periods as a 32-bit
         * integer, with the top 16 bits representing on cycles, and the
         * bottom 16 representing off cycles.
         * 
         * LED duty cycle values have three different formats:
         * 
         * - SPROM:     A 16-bit unsigned integer, with on/off cycles encoded
         *              as 8-bit values.
         * - NVRAM:     A 16-bit decimal or hexadecimal string, with on/off
         *              cycles encoded as 8-bit values as per the SPROM format.
         * - NVRAM:     A 32-bit decimal or hexadecimal string, with on/off
         *              cycles encoded as 16-bit values.
         *
         * To convert from a 16-bit representation to a 32-bit representation:
         *     ((value & 0xFF00) << 16) | ((value & 0x00FF) << 8)
         * 
         * To convert from a 32-bit representation to a 16-bit representation,
         * perform the same operation in reverse, discarding the lower 8-bits
         * of each half of the 32-bit representation:
         *     ((value >> 16) & 0xFF00) | ((value >> 8) & 0x00FF)
         */

        itype = bhnd_nvram_val_elem_type(value);
        nbytes = 0;
        led16_lossy = false;

        /* Determine output byte limit */
        if (outp != NULL)
                limit = *olen;
        else
                limit = 0;

        /* If the input/output types match, just delegate to standard value
         * encoding support */
        if (otype == itype) {
                return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen,
                    otype));
        }

        /* If our value is a string, it may either be a 16-bit or a 32-bit
         * representation of the duty cycle */
        if (itype == BHND_NVRAM_TYPE_STRING) {
                const char      *p;
                uint32_t         ival;
                size_t           nlen, parsed;

                /* Parse integer value */
                p = inp;
                nlen = sizeof(ival);
                error = bhnd_nvram_parse_int(p, ilen, 0, &parsed, &ival, &nlen,
                    BHND_NVRAM_TYPE_UINT32);
                if (error)
                        return (error);

                /* Trailing garbage? */
                if (parsed < ilen && *(p+parsed) != '\0')
                        return (EFTYPE);

                /* Point inp and itype to either our parsed 32-bit or 16-bit
                 * value */
                inp = &strval;
                if (ival & 0xFFFF0000) {
                        strval.u32 = ival;
                        itype = BHND_NVRAM_TYPE_UINT32;
                } else {
                        strval.u16 = ival;
                        itype = BHND_NVRAM_TYPE_UINT16;
                }
        }

        /* Populate both u32 and (possibly lossy) u16 LEDDC representations */
        switch (itype) {
        case BHND_NVRAM_TYPE_UINT16: {
                led16 = *(const uint16_t *)inp;
                led32 = ((led16 & 0xFF00) << 16) | ((led16 & 0x00FF) << 8);

                /* If all bits are set in the 16-bit value (indicating that
                 * the value is 'unset' in SPROM), we must update the 32-bit
                 * representation to match. */
                if (led16 == UINT16_MAX)
                        led32 = UINT32_MAX;

                break;
        }

        case BHND_NVRAM_TYPE_UINT32:
                led32 = *(const uint32_t *)inp;
                led16 = ((led32 >> 16) & 0xFF00) | ((led32 >> 8) & 0x00FF);

                /*
                 * Determine whether the led16 conversion is lossy:
                 * 
                 * - If the lower 8 bits of each half of the 32-bit value
                 *   aren't set, we can safely use the 16-bit representation
                 *   without losing data.
                 * - If all bits in the 32-bit value are set, the variable is
                 *   treated as unset in  SPROM. We can safely use the 16-bit
                 *   representation without losing data.
                 */
                if ((led32 & 0x00FF00FF) != 0 && led32 != UINT32_MAX)
                        led16_lossy = true;

                break;
        default:
                BHND_NV_PANIC("unsupported backing data type: %s",
                    bhnd_nvram_type_name(itype));
        }

        /*
         * Encode as requested output type.
         */
        switch (otype) {
        case BHND_NVRAM_TYPE_STRING:
                /*
                 * Prefer 16-bit format.
                 */
                if (!led16_lossy) {
                        return (bhnd_nvram_value_printf("0x%04hX", &led16,
                            sizeof(led16), BHND_NVRAM_TYPE_UINT16, outp, olen));
                } else {
                        return (bhnd_nvram_value_printf("0x%04X", &led32,
                            sizeof(led32), BHND_NVRAM_TYPE_UINT32, outp, olen));
                }

                break;

        case BHND_NVRAM_TYPE_UINT16: {
                /* Can we encode as uint16 without losing data? */
                if (led16_lossy)
                        return (ERANGE);

                /* Write led16 format */
                nbytes += sizeof(uint16_t);
                if (limit >= nbytes)
                        *(uint16_t *)outp = led16;

                break;
        }

        case BHND_NVRAM_TYPE_UINT32:
                /* Write led32 format */
                nbytes += sizeof(uint32_t);
                if (limit >= nbytes)
                        *(uint32_t *)outp = led32;
                break;

        default:
                /* No other output formats are supported */
                return (EFTYPE);
        }

        /* Provide the actual length */
        *olen = nbytes;

        /* Report insufficient space (if output was requested) */
        if (limit < nbytes && outp != NULL)
                return (ENOMEM);

        return (0);
}

/**
 * Broadcom NVRAM string encoding.
 */
static int
bhnd_nvram_val_bcmstr_encode(bhnd_nvram_val *value, void *outp, size_t *olen,
    bhnd_nvram_type otype)
{
        bhnd_nvram_val                   array;
        const bhnd_nvram_val_fmt        *array_fmt;
        const void                      *inp;
        bhnd_nvram_type                 itype;
        size_t                          ilen;
        int                             error;

        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);

        /* If the output is not an array type (or if it's a character array),
         * we can fall back on standard string encoding */
        if (!bhnd_nvram_is_array_type(otype) ||
            otype == BHND_NVRAM_TYPE_CHAR_ARRAY)
        {
                return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen,
                    otype));
        }

        /* Otherwise, we need to interpret our value as either a macaddr
         * string, or a comma-delimited string. */
        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
        if (bhnd_nvram_ident_octet_string(inp, ilen, NULL, NULL))
                array_fmt = &bhnd_nvram_val_bcm_macaddr_string_fmt;
        else
                array_fmt = &bhnd_nvram_val_bcm_string_csv_fmt;

        /* Wrap in array-typed representation */
        error = bhnd_nvram_val_init(&array, array_fmt, inp, ilen, itype,
            BHND_NVRAM_VAL_BORROW_DATA);
        if (error) {
                BHND_NV_LOG("error initializing array representation: %d\n",
                    error);
                return (error);
        }

        /* Ask the array-typed value to perform the encode */
        error = bhnd_nvram_val_encode(&array, outp, olen, otype);
        if (error)
                BHND_NV_LOG("error encoding array representation: %d\n", error);

        bhnd_nvram_val_release(&array);

        return (error);
}

/**
 * Broadcom NVRAM comma-delimited string filter.
 */
static int
bhnd_nvram_val_bcmstr_csv_filter(const bhnd_nvram_val_fmt **fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype)
{
        switch (itype) {
        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY:
                return (0);
        default:
                return (EFTYPE);
        }
}

/**
 * Broadcom NVRAM comma-delimited string iteration.
 */
static const void *
bhnd_nvram_val_bcmstr_csv_next(bhnd_nvram_val *value, const void *prev,
    size_t *len)
{
        const char      *next;
        const char      *inp;
        bhnd_nvram_type  itype;
        size_t           ilen, remain;
        char             delim;

        /* Fetch backing representation */
        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);

        /* Fetch next value */
        switch (itype) {
        case BHND_NVRAM_TYPE_STRING:
                /* Zero-length array? */
                if (ilen == 0)
                        return (NULL);

                if (prev == NULL) {
                        /* First element */
                        next = inp;
                        remain = ilen;
                        delim = ',';
                } else {
                        /* Advance to the previous element's delimiter */
                        next = (const char *)prev + *len;

                        /* Did we hit the end of the string? */
                        if ((size_t)(next - inp) >= ilen)
                                return (NULL);

                        /* Fetch (and skip past) the delimiter */
                        delim = *next;
                        next++;
                        remain = ilen - (size_t)(next - inp);

                        /* Was the delimiter the final character? */
                        if (remain == 0)
                                return (NULL);
                }

                /* Parse the field value, up to the next delimiter */
                *len = bhnd_nvram_parse_field(&next, remain, delim);

                return (next);

        case BHND_NVRAM_TYPE_STRING_ARRAY:
                /* Delegate to default array iteration */
                return (bhnd_nvram_value_array_next(inp, ilen, itype, prev,
                    len));
        default:
                BHND_NV_PANIC("unsupported type: %d", itype);
        }
}

/**
 * MAC address filter.
 */
static int
bhnd_nvram_val_bcm_macaddr_filter(const bhnd_nvram_val_fmt **fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype)
{
        switch (itype) {
        case BHND_NVRAM_TYPE_UINT8_ARRAY:
                return (0);
        case BHND_NVRAM_TYPE_STRING:
                /* Let bcm_macaddr_string format handle it */
                *fmt = &bhnd_nvram_val_bcm_macaddr_string_fmt;
                return (0);
        default:
                return (EFTYPE);
        }
}

/**
 * MAC address encoding.
 */
static int
bhnd_nvram_val_bcm_macaddr_encode(bhnd_nvram_val *value, void *outp,
    size_t *olen, bhnd_nvram_type otype)
{
        const void      *inp;
        bhnd_nvram_type  itype;
        size_t           ilen;

        /*
         * If converting to a string (or a single-element string array),
         * produce an octet string (00:00:...).
         */
        if (bhnd_nvram_base_type(otype) == BHND_NVRAM_TYPE_STRING) {
                return (bhnd_nvram_val_printf(value, "%[]02hhX", outp, olen,
                    ":"));
        }

        /* Otherwise, use standard encoding support */
        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
        return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen, otype));}

/**
 * MAC address string filter.
 */
static int
bhnd_nvram_val_bcm_macaddr_string_filter(const bhnd_nvram_val_fmt **fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype)
{
        switch (itype) {
        case BHND_NVRAM_TYPE_STRING:
                /* Use the standard Broadcom string format implementation if
                 * the input is not an octet string. */
                if (!bhnd_nvram_ident_octet_string(inp, ilen, NULL, NULL))
                        *fmt = &bhnd_nvram_val_bcm_string_fmt;

                return (0);
        default:
                return (EFTYPE);
        }
}

/**
 * MAC address string octet encoding.
 */
static int
bhnd_nvram_val_bcm_macaddr_string_encode_elem(bhnd_nvram_val *value,
    const void *inp, size_t ilen, void *outp, size_t *olen,
    bhnd_nvram_type otype)
{
        size_t  nparsed;
        int     error;

        /* If integer encoding is requested, explicitly parse our
         * non-0x-prefixed as a base 16 integer value */
        if (bhnd_nvram_is_int_type(otype)) {
                error = bhnd_nvram_parse_int(inp, ilen, 16, &nparsed, outp,
                    olen, otype);
                if (error)
                        return (error);

                if (nparsed != ilen)
                        return (EFTYPE);

                return (0);
        }

        /* Otherwise, use standard encoding support */
        return (bhnd_nvram_value_coerce(inp, ilen,
            bhnd_nvram_val_elem_type(value), outp, olen, otype));
}

/**
 * MAC address string octet iteration.
 */
static const void *
bhnd_nvram_val_bcm_macaddr_string_next(bhnd_nvram_val *value, const void *prev,
    size_t *len)
{
        const char      *next;
        const char      *str;
        bhnd_nvram_type  stype;
        size_t           slen, remain;
        char             delim;

        /* Fetch backing string */
        str = bhnd_nvram_val_bytes(value, &slen, &stype);
        BHND_NV_ASSERT(stype == BHND_NVRAM_TYPE_STRING,
            ("unsupported type: %d", stype));

        /* Zero-length array? */
        if (slen == 0)
                return (NULL);

        if (prev == NULL) {
                /* First element */

                /* Determine delimiter */
                if (!bhnd_nvram_ident_octet_string(str, slen, &delim, NULL)) {
                        /* Default to comma-delimited parsing */
                        delim = ',';
                }

                /* Parsing will start at the base string pointer */
                next = str;
                remain = slen;
        } else {
                /* Advance to the previous element's delimiter */
                next = (const char *)prev + *len;

                /* Did we hit the end of the string? */
                if ((size_t)(next - str) >= slen)
                        return (NULL);

                /* Fetch (and skip past) the delimiter */
                delim = *next;
                next++;
                remain = slen - (size_t)(next - str);

                /* Was the delimiter the final character? */
                if (remain == 0)
                        return (NULL);
        }

        /* Parse the field value, up to the next delimiter */
        *len = bhnd_nvram_parse_field(&next, remain, delim);

        return (next);
}

/**
 * Determine whether @p inp is in octet string format, consisting of a
 * fields of two hex characters, separated with ':' or '-' delimiters.
 * 
 * This may be used to identify MAC address octet strings
 * (BHND_NVRAM_SFMT_MACADDR).
 *
 * @param               inp     The string to be parsed.
 * @param               ilen    The length of @p inp, in bytes.
 * @param[out]          delim   On success, the delimiter used by this octet
 *                              string. May be set to NULL if the field
 *                              delimiter is not desired.
 * @param[out]          nelem   On success, the number of fields in this
 *                              octet string. May be set to NULL if the field
 *                              count is not desired.
 *
 * 
 * @retval true         if @p inp is a valid octet string
 * @retval false        if @p inp is not a valid octet string.
 */
static bool
bhnd_nvram_ident_octet_string(const char *inp, size_t ilen, char *delim,
    size_t *nelem)
{
        size_t  elem_count;
        size_t  max_elem_count, min_elem_count;
        size_t  field_count;
        char    idelim;

        field_count = 0;

        /* Require exactly two digits. If we relax this, there is room
         * for ambiguity with signed integers and the '-' delimiter */
        min_elem_count = 2;
        max_elem_count = 2;

        /* Identify the delimiter used. The standard delimiter for MAC
         * addresses is ':', but some earlier NVRAM formats may use '-' */
        for (const char *d = ":-";; d++) {
                const char *loc;

                /* No delimiter found, not an octet string */
                if (*d == '\0')
                        return (false);

                /* Look for the delimiter */
                if ((loc = memchr(inp, *d, ilen)) == NULL)
                        continue;

                /* Delimiter found */
                idelim = *loc;
                break;
        }

        /* To disambiguate from signed integers, if the delimiter is "-",
         * the octets must be exactly 2 chars each */
        if (idelim == '-')
                min_elem_count = 2;

        /* String must be composed of individual octets (zero or more hex
         * digits) separated by our delimiter. */
        elem_count = 0;
        for (const char *p = inp; (size_t)(p - inp) < ilen; p++) {
                switch (*p) {
                case ':':
                case '-':
                case '\0':
                        /* Hit a delim character; all delims must match
                         * the first delimiter used */
                        if (*p != '\0' && *p != idelim)
                                return (false);

                        /* Must have parsed at least min_elem_count digits */
                        if (elem_count < min_elem_count)
                                return (false);

                        /* Reset element count */
                        elem_count = 0;

                        /* Bump field count */
                        field_count++;
                        break;
                default:
                        /* More than maximum number of hex digits? */
                        if (elem_count >= max_elem_count)
                                return (false);

                        /* Octet values must be hex digits */
                        if (!bhnd_nv_isxdigit(*p))
                                return (false);

                        elem_count++;
                        break;
                }
        }

        if (delim != NULL)
                *delim = idelim;

        if (nelem != NULL)
                *nelem = field_count;

        return (true);
}

/**
 * Determine whether @p inp is in hexadecimal, octal, or decimal string
 * format.
 *
 * - A @p str may be prefixed with a single optional '+' or '-' sign denoting
 *   signedness.
 * - A hexadecimal @p str may include an '0x' or '0X' prefix, denoting that a
 *   base 16 integer follows.
 * - An octal @p str may include a '0' prefix, denoting that an octal integer
 *   follows.
 * 
 * @param       inp     The string to be parsed.
 * @param       ilen    The length of @p inp, in bytes.
 * @param       base    The input string's base (2-36), or 0.
 * @param[out]  obase   On success, will be set to the base of the parsed
 *                      integer. May be set to NULL if the base is not
 *                      desired.
 *
 * @retval true         if @p inp is a valid number string
 * @retval false        if @p inp is not a valid number string.
 * @retval false        if @p base is invalid.
 */
static bool
bhnd_nvram_ident_num_string(const char *inp, size_t ilen, u_int base,
    u_int *obase)
{
        size_t  nbytes, ndigits;

        nbytes = 0;
        ndigits = 0;

        /* Parse and skip sign */
        if (nbytes >= ilen)
                return (false);

        if (inp[nbytes] == '-' || inp[nbytes] == '+')
                nbytes++;

        /* Truncated after sign character? */
        if (nbytes == ilen)
                return (false);

        /* Identify (or validate) hex base, skipping 0x/0X prefix */
        if (base == 16 || base == 0) {
                /* Check for (and skip) 0x/0X prefix */
                if (ilen - nbytes >= 2 && inp[nbytes] == '0' &&
                    (inp[nbytes+1] == 'x' || inp[nbytes+1] == 'X'))
                {
                        base = 16;
                        nbytes += 2;
                }
        }

        /* Truncated after hex prefix? */
        if (nbytes == ilen)
                return (false);

        /* Differentiate decimal/octal by looking for a leading 0 */
        if (base == 0) {
                if (inp[nbytes] == '0') {
                        base = 8;
                } else {
                        base = 10;
                }
        }

        /* Consume and validate all remaining digit characters */
        for (; nbytes < ilen; nbytes++) {
                u_int   carry;
                char    c;

                /* Parse carry value */
                c = inp[nbytes];
                if (bhnd_nv_isdigit(c)) {
                        carry = c - '0';
                } else if (bhnd_nv_isxdigit(c)) {
                        if (bhnd_nv_isupper(c))
                                carry = (c - 'A') + 10;
                        else
                                carry = (c - 'a') + 10;
                } else {
                        /* Hit a non-digit character */
                        return (false);
                }

                /* If carry is outside the base, it's not a valid digit
                 * in the current parse context; consider it a non-digit
                 * character */
                if (carry >= base)
                        return (false);

                /* Increment parsed digit count */
                ndigits++;
        }

        /* Empty integer string? */
        if (ndigits == 0)
                return (false);

        /* Valid integer -- provide the base and return */
        if (obase != NULL)
                *obase = base;
        return (true);
}