root/sys/dev/bhnd/nvram/bhnd_nvram_value.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 <sys/limits.h>
#include <sys/sbuf.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 <inttypes.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#endif /* _KERNEL */

#include "bhnd_nvram_private.h"

#include "bhnd_nvram_valuevar.h"

static int       bhnd_nvram_val_fmt_filter(const bhnd_nvram_val_fmt **fmt,
                     const void *inp, size_t ilen, bhnd_nvram_type itype);

static void     *bhnd_nvram_val_alloc_bytes(bhnd_nvram_val *value, size_t ilen,
                     bhnd_nvram_type itype, uint32_t flags);
static int       bhnd_nvram_val_set(bhnd_nvram_val *value, const void *inp,
                     size_t ilen, bhnd_nvram_type itype, uint32_t flags);
static int       bhnd_nvram_val_set_inline(bhnd_nvram_val *value,
                     const void *inp, size_t ilen, bhnd_nvram_type itype);

static int       bhnd_nvram_val_encode_data(const void *inp, size_t ilen,
                     bhnd_nvram_type itype, void *outp, size_t *olen,
                     bhnd_nvram_type otype);
static int       bhnd_nvram_val_encode_int(const void *inp, size_t ilen,
                     bhnd_nvram_type itype, void *outp, size_t *olen,
                     bhnd_nvram_type otype);
static int       bhnd_nvram_val_encode_null(const void *inp, size_t ilen,
                     bhnd_nvram_type itype, void *outp, size_t *olen,
                     bhnd_nvram_type otype);
static int       bhnd_nvram_val_encode_bool(const void *inp, size_t ilen,
                     bhnd_nvram_type itype, void *outp, size_t *olen,
                     bhnd_nvram_type otype);
static int       bhnd_nvram_val_encode_string(const void *inp, size_t ilen,
                     bhnd_nvram_type itype, void *outp, size_t *olen,
                     bhnd_nvram_type otype);

/** Initialize an empty value instance with @p _fmt, @p _storage, and
 *  an implicit callee-owned reference */
#define BHND_NVRAM_VAL_INITIALIZER(_fmt, _storage)              \
        (bhnd_nvram_val) {                                      \
                .refs = 1,                                      \
                .val_storage = _storage,                        \
                .fmt = _fmt,                                    \
                .data_storage = BHND_NVRAM_VAL_DATA_NONE,       \
        };

/** Assert that @p value's backing representation state has initialized
 *  as empty. */
#define BHND_NVRAM_VAL_ASSERT_EMPTY(_value)                     \
        BHND_NV_ASSERT(                                         \
            value->data_storage == BHND_NVRAM_VAL_DATA_NONE &&  \
            value->data_len == 0 &&                             \
            value->data.ptr == NULL,                            \
            ("previously initialized value"))

/** Return true if BHND_NVRAM_VAL_BORROW_DATA or BHND_NVRAM_VAL_STATIC_DATA is
 *  set in @p _flags (e.g. we should attempt to directly reference external
 *  data */
#define BHND_NVRAM_VAL_EXTREF_BORROWED_DATA(_flags)             \
        (((_flags) & BHND_NVRAM_VAL_BORROW_DATA) ||             \
         ((_flags) & BHND_NVRAM_VAL_STATIC_DATA))

/** Flags permitted when performing val-based initialization via
 *  bhnd_nvram_val_convert_init() or bhnd_nvram_val_convert_new() */
#define BHND_NVRAM_VALID_CONV_FLAGS     \
        (BHND_NVRAM_VAL_FIXED |         \
         BHND_NVRAM_VAL_DYNAMIC |       \
         BHND_NVRAM_VAL_COPY_DATA)

/** Returns true if @p _val must be copied in bhnd_nvram_val_copy(), false
 *  if its reference count may be safely incremented */
#define BHND_NVRAM_VAL_NEED_COPY(_val)                          \
        ((_val)->val_storage == BHND_NVRAM_VAL_STORAGE_AUTO ||  \
         (_val)->data_storage == BHND_NVRAM_VAL_DATA_EXT_WEAK)

volatile u_int                   refs;          /**< reference count */
bhnd_nvram_val_storage           val_storage;   /**< value structure storage */
const bhnd_nvram_val_fmt        *fmt;           /**< value format */
bhnd_nvram_val_data_storage      data_storage;  /**< data storage */
bhnd_nvram_type                  data_type;     /**< data type */
size_t                           data_len;      /**< data size */

/* Shared NULL value instance */
bhnd_nvram_val bhnd_nvram_val_null = {
        .refs           = 1,
        .val_storage    = BHND_NVRAM_VAL_STORAGE_STATIC,
        .fmt            = &bhnd_nvram_val_null_fmt,
        .data_storage   = BHND_NVRAM_VAL_DATA_INLINE,
        .data_type      = BHND_NVRAM_TYPE_NULL,
        .data_len       = 0,
};

/**
 * Return the human-readable name of @p fmt.
 */
const char *
bhnd_nvram_val_fmt_name(const bhnd_nvram_val_fmt *fmt)
{
        return (fmt->name);
}

/**
 * Return the default format for values of @p type.
 */
const bhnd_nvram_val_fmt *
bhnd_nvram_val_default_fmt(bhnd_nvram_type type)
{
        switch (type) {
        case BHND_NVRAM_TYPE_UINT8:
                return (&bhnd_nvram_val_uint8_fmt);
        case BHND_NVRAM_TYPE_UINT16:
                return (&bhnd_nvram_val_uint16_fmt);
        case BHND_NVRAM_TYPE_UINT32:
                return (&bhnd_nvram_val_uint32_fmt);
        case BHND_NVRAM_TYPE_UINT64:
                return (&bhnd_nvram_val_uint64_fmt);
        case BHND_NVRAM_TYPE_INT8:
                return (&bhnd_nvram_val_int8_fmt);
        case BHND_NVRAM_TYPE_INT16:
                return (&bhnd_nvram_val_int16_fmt);
        case BHND_NVRAM_TYPE_INT32:
                return (&bhnd_nvram_val_int32_fmt);
        case BHND_NVRAM_TYPE_INT64:
                return (&bhnd_nvram_val_int64_fmt);
        case BHND_NVRAM_TYPE_CHAR:
                return (&bhnd_nvram_val_char_fmt);
        case BHND_NVRAM_TYPE_STRING:
                return (&bhnd_nvram_val_string_fmt);
        case BHND_NVRAM_TYPE_BOOL:
                return (&bhnd_nvram_val_bool_fmt);
        case BHND_NVRAM_TYPE_NULL:
                return (&bhnd_nvram_val_null_fmt);
        case BHND_NVRAM_TYPE_DATA:
                return (&bhnd_nvram_val_data_fmt);
        case BHND_NVRAM_TYPE_UINT8_ARRAY:
                return (&bhnd_nvram_val_uint8_array_fmt);
        case BHND_NVRAM_TYPE_UINT16_ARRAY:
                return (&bhnd_nvram_val_uint16_array_fmt);
        case BHND_NVRAM_TYPE_UINT32_ARRAY:
                return (&bhnd_nvram_val_uint32_array_fmt);
        case BHND_NVRAM_TYPE_UINT64_ARRAY:
                return (&bhnd_nvram_val_uint64_array_fmt);
        case BHND_NVRAM_TYPE_INT8_ARRAY:
                return (&bhnd_nvram_val_int8_array_fmt);
        case BHND_NVRAM_TYPE_INT16_ARRAY:
                return (&bhnd_nvram_val_int16_array_fmt);
        case BHND_NVRAM_TYPE_INT32_ARRAY:
                return (&bhnd_nvram_val_int32_array_fmt);
        case BHND_NVRAM_TYPE_INT64_ARRAY:
                return (&bhnd_nvram_val_int64_array_fmt);
        case BHND_NVRAM_TYPE_CHAR_ARRAY:
                return (&bhnd_nvram_val_char_array_fmt);
        case BHND_NVRAM_TYPE_STRING_ARRAY:
                return (&bhnd_nvram_val_string_array_fmt);
        case BHND_NVRAM_TYPE_BOOL_ARRAY:
                return (&bhnd_nvram_val_bool_array_fmt);
        }

        /* Quiesce gcc4.2 */
        BHND_NV_PANIC("bhnd nvram type %u unknown", type);
}

/**
 * Determine whether @p fmt (or new format delegated to by @p fmt) is
 * capable of direct initialization from buffer @p inp.
 * 
 * @param[in,out]       fmt     Indirect pointer to the NVRAM value format. If
 *                              the format instance cannot handle the data type
 *                              directly, it may delegate to a new format
 *                              instance. On success, this parameter will be
 *                              set to the format that should be used when
 *                              performing initialization from @p inp.
 * @param               inp     Input data.
 * @param               ilen    Input data length.
 * @param               itype   Input data type.
 *
 * @retval 0            If initialization from @p inp is supported.
 * @retval EFTYPE       If initialization from @p inp is unsupported.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 */
static int
bhnd_nvram_val_fmt_filter(const bhnd_nvram_val_fmt **fmt, const void *inp,
    size_t ilen, bhnd_nvram_type itype)
{
        const bhnd_nvram_val_fmt        *ofmt, *nfmt;
        int                              error;

        nfmt = ofmt = *fmt;

        /* Validate alignment */
        if ((error = bhnd_nvram_value_check_aligned(inp, ilen, itype)))
                return (error);

        /* If the format does not provide a filter function, it only supports
         * direct initialization from its native type */
        if (ofmt->op_filter == NULL) {
                if (itype == ofmt->native_type)
                        return (0);

                return (EFTYPE);
        }

        /* Use the filter function to determine whether direct initialization
         * from itype is permitted */
        error = ofmt->op_filter(&nfmt, inp, ilen, itype);
        if (error)
                return (error);

        /* Retry filter with new format? */
        if (ofmt != nfmt) {
                error = bhnd_nvram_val_fmt_filter(&nfmt, inp, ilen, itype);
                if (error)
                        return (error);

                /* Success -- provide delegated format to caller */
                *fmt = nfmt;
        }

        /* Value can be initialized with provided format and input type */
        return (0);
}

/* Common initialization support for bhnd_nvram_val_init() and
 * bhnd_nvram_val_new() */
static int
bhnd_nvram_val_init_common(bhnd_nvram_val *value,
    bhnd_nvram_val_storage val_storage, const bhnd_nvram_val_fmt *fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype, uint32_t flags)
{
        void            *outp;
        bhnd_nvram_type  otype;
        size_t           olen;
        int              error;

        /* If the value format is unspecified, we use the default format
         * for the input data type */
        if (fmt == NULL)
                fmt = bhnd_nvram_val_default_fmt(itype);

        /* Determine expected data type, and allow the format to delegate to
         * a new format instance */
        if ((error = bhnd_nvram_val_fmt_filter(&fmt, inp, ilen, itype))) {
                /* Direct initialization from the provided input type is
                 * not supported; alue must be initialized with the format's
                 * native type */
                otype = fmt->native_type;
        } else {
                /* Value can be initialized with provided input type */
                otype = itype;
        }

        /* Initialize value instance */
        *value = BHND_NVRAM_VAL_INITIALIZER(fmt, val_storage);

        /* If input data already in native format, init directly. */
        if (otype == itype) {
                error = bhnd_nvram_val_set(value, inp, ilen, itype, flags);
                if (error)
                        return (error);

                return (0);
        }

        /* Determine size when encoded in native format */
        error = bhnd_nvram_value_coerce(inp, ilen, itype, NULL, &olen, otype);
        if (error)
                return (error);

        /* Fetch reference to (or allocate) an appropriately sized buffer */
        outp = bhnd_nvram_val_alloc_bytes(value, olen, otype, flags);
        if (outp == NULL)
                return (ENOMEM);

        /* Perform encode */
        error = bhnd_nvram_value_coerce(inp, ilen, itype, outp, &olen, otype);
        if (error)
                return (error);

        return (0);
}

/**
 * Initialize an externally allocated instance of @p value with @p fmt from the
 * given @p inp buffer of @p itype and @p ilen.
 *
 * On success, the caller owns a reference to @p value, and is responsible for
 * freeing any resources allocated for @p value via bhnd_nvram_val_release().
 *
 * @param       value   The externally allocated value instance to be
 *                      initialized.
 * @param       fmt     The value's format, or NULL to use the default format
 *                      for @p itype.
 * @param       inp     Input buffer.
 * @param       ilen    Input buffer length.
 * @param       itype   Input buffer type.
 * @param       flags   Value flags (see BHND_NVRAM_VAL_*).
 * 
 * @retval 0            success
 * @retval ENOMEM       If allocation fails.
 * @retval EFTYPE       If @p fmt initialization from @p itype is unsupported.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 * @retval ERANGE       If value coercion would overflow (or underflow) the
 *                      @p fmt representation.
 */
int
bhnd_nvram_val_init(bhnd_nvram_val *value, const bhnd_nvram_val_fmt *fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype, uint32_t flags)
{
        int error;

        error = bhnd_nvram_val_init_common(value, BHND_NVRAM_VAL_STORAGE_AUTO,
            fmt, inp, ilen, itype, flags);
        if (error)
                bhnd_nvram_val_release(value);

        return (error);
}

/**
 * Allocate a value instance with @p fmt, and attempt to initialize its internal
 * representation from the given @p inp buffer of @p itype and @p ilen.
 *
 * On success, the caller owns a reference to @p value, and is responsible for
 * freeing any resources allocated for @p value via bhnd_nvram_val_release().
 *
 * @param[out]  value   On success, the allocated value instance.
 * @param       fmt     The value's format, or NULL to use the default format
 *                      for @p itype.
 * @param       inp     Input buffer.
 * @param       ilen    Input buffer length.
 * @param       itype   Input buffer type.
 * @param       flags   Value flags (see BHND_NVRAM_VAL_*).
 * 
 * @retval 0            success
 * @retval ENOMEM       If allocation fails.
 * @retval EFTYPE       If @p fmt initialization from @p itype is unsupported.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 * @retval ERANGE       If value coercion would overflow (or underflow) the
 *                      @p fmt representation.
 */
int
bhnd_nvram_val_new(bhnd_nvram_val **value, const bhnd_nvram_val_fmt *fmt,
    const void *inp, size_t ilen, bhnd_nvram_type itype, uint32_t flags)
{
        int error;

        /* Allocate new instance */
        if ((*value = bhnd_nv_malloc(sizeof(**value))) == NULL)
                return (ENOMEM);

        /* Perform common initialization. */
        error = bhnd_nvram_val_init_common(*value,
            BHND_NVRAM_VAL_STORAGE_DYNAMIC, fmt, inp, ilen, itype, flags);
        if (error) {
                /* Will also free() the value allocation */
                bhnd_nvram_val_release(*value);
        }

        return (error);
}

/* Common initialization support for bhnd_nvram_val_convert_init() and
 * bhnd_nvram_val_convert_new() */
static int
bhnd_nvram_val_convert_common(bhnd_nvram_val *value,
    bhnd_nvram_val_storage val_storage, const bhnd_nvram_val_fmt *fmt,
    bhnd_nvram_val *src, uint32_t flags)
{
        const void      *inp;
        void            *outp;
        bhnd_nvram_type  itype, otype;
        size_t           ilen, olen;
        int              error;

        /* Determine whether direct initialization from the source value's
         * existing data type is supported by the new format */
        inp = bhnd_nvram_val_bytes(src, &ilen, &itype);
        if (bhnd_nvram_val_fmt_filter(&fmt, inp, ilen, itype) == 0) {
                /* Adjust value flags based on the source data storage */
                switch (src->data_storage) {
                case BHND_NVRAM_VAL_DATA_NONE:
                case BHND_NVRAM_VAL_DATA_INLINE:
                case BHND_NVRAM_VAL_DATA_EXT_WEAK:
                case BHND_NVRAM_VAL_DATA_EXT_ALLOC:
                        break;

                case BHND_NVRAM_VAL_DATA_EXT_STATIC:
                        /* If the source data has static storage duration,
                         * we should apply that transitively */
                        if (flags & BHND_NVRAM_VAL_BORROW_DATA)
                                flags |= BHND_NVRAM_VAL_STATIC_DATA;

                        break;
                }

                /* Delegate to standard initialization */
                return (bhnd_nvram_val_init_common(value, val_storage, fmt, inp,
                    ilen, itype, flags));
        } 

        /* Value must be initialized with the format's native type */
        otype = fmt->native_type;

        /* Initialize value instance */
        *value = BHND_NVRAM_VAL_INITIALIZER(fmt, val_storage);

        /* Determine size when encoded in native format */
        if ((error = bhnd_nvram_val_encode(src, NULL, &olen, otype)))
                return (error);

        /* Fetch reference to (or allocate) an appropriately sized buffer */
        outp = bhnd_nvram_val_alloc_bytes(value, olen, otype, flags);
        if (outp == NULL)
                return (ENOMEM);

        /* Perform encode */
        if ((error = bhnd_nvram_val_encode(src, outp, &olen, otype)))
                return (error);

        return (0);
}

/**
 * Initialize an externally allocated instance of @p value with @p fmt, and
 * attempt to initialize its internal representation from the given @p src
 * value.
 *
 * On success, the caller owns a reference to @p value, and is responsible for
 * freeing any resources allocated for @p value via bhnd_nvram_val_release().
 *
 * @param       value   The externally allocated value instance to be
 *                      initialized.
 * @param       fmt     The value's format.
 * @param       src     Input value to be converted.
 * @param       flags   Value flags (see BHND_NVRAM_VAL_*).
 * 
 * @retval 0            success
 * @retval ENOMEM       If allocation fails.
 * @retval EFTYPE       If @p fmt initialization from @p src is unsupported.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 * @retval ERANGE       If value coercion of @p src would overflow
 *                      (or underflow) the @p fmt representation.
 */
int
bhnd_nvram_val_convert_init(bhnd_nvram_val *value,
    const bhnd_nvram_val_fmt *fmt, bhnd_nvram_val *src, uint32_t flags)
{
        int error;

        error = bhnd_nvram_val_convert_common(value,
            BHND_NVRAM_VAL_STORAGE_AUTO, fmt, src, flags);
        if (error)
                bhnd_nvram_val_release(value);

        return (error);
}

/**
 * Allocate a value instance with @p fmt, and attempt to initialize its internal
 * representation from the given @p src value.
 *
 * On success, the caller owns a reference to @p value, and is responsible for
 * freeing any resources allocated for @p value via bhnd_nvram_val_release().
 *
 * @param[out]  value   On success, the allocated value instance.
 * @param       fmt     The value's format.
 * @param       src     Input value to be converted.
 * @param       flags   Value flags (see BHND_NVRAM_VAL_*).
 * 
 * @retval 0            success
 * @retval ENOMEM       If allocation fails.
 * @retval EFTYPE       If @p fmt initialization from @p src is unsupported.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 * @retval ERANGE       If value coercion of @p src would overflow
 *                      (or underflow) the @p fmt representation.
 */
int
bhnd_nvram_val_convert_new(bhnd_nvram_val **value,
    const bhnd_nvram_val_fmt *fmt, bhnd_nvram_val *src, uint32_t flags)
{
        int error;

        /* Allocate new instance */
        if ((*value = bhnd_nv_malloc(sizeof(**value))) == NULL)
                return (ENOMEM);

        /* Perform common initialization. */
        error = bhnd_nvram_val_convert_common(*value,
            BHND_NVRAM_VAL_STORAGE_DYNAMIC, fmt, src, flags);
        if (error) {
                /* Will also free() the value allocation */
                bhnd_nvram_val_release(*value);
        }

        return (error);
}

/**
 * Copy or retain a reference to @p value.
 * 
 * On success, the caller is responsible for freeing the result via
 * bhnd_nvram_val_release().
 * 
 * @param       value   The value to be copied (or retained).
 * 
 * @retval bhnd_nvram_val       if @p value was successfully copied or retained.
 * @retval NULL                 if allocation failed.
 */
bhnd_nvram_val *
bhnd_nvram_val_copy(bhnd_nvram_val *value)
{
        bhnd_nvram_val          *result;
        const void              *bytes;
        bhnd_nvram_type          type;
        size_t                   len;
        uint32_t                 flags;
        int                      error;

        switch (value->val_storage) {
        case BHND_NVRAM_VAL_STORAGE_STATIC:
                /* If static, can return as-is */
                return (value);

        case BHND_NVRAM_VAL_STORAGE_DYNAMIC:
                if (!BHND_NVRAM_VAL_NEED_COPY(value)) {
                        refcount_acquire(&value->refs);
                        return (value);
                }

                /* Perform copy below */
                break;

        case BHND_NVRAM_VAL_STORAGE_AUTO:
                BHND_NV_ASSERT(value->refs == 1, ("non-allocated value has "
                    "active refcount (%u)", value->refs));

                /* Perform copy below */
                break;
        }

        /* Compute the new value's flags based on the source value */
        switch (value->data_storage) {
        case BHND_NVRAM_VAL_DATA_NONE:
        case BHND_NVRAM_VAL_DATA_INLINE:
        case BHND_NVRAM_VAL_DATA_EXT_WEAK:
        case BHND_NVRAM_VAL_DATA_EXT_ALLOC:
                /* Copy the source data and permit additional allocation if the
                 * value cannot be represented inline */
                flags = BHND_NVRAM_VAL_COPY_DATA|BHND_NVRAM_VAL_DYNAMIC;
                break;
        case BHND_NVRAM_VAL_DATA_EXT_STATIC:
                flags = BHND_NVRAM_VAL_STATIC_DATA;
                break;
        default:
                BHND_NV_PANIC("invalid storage type: %d", value->data_storage);
        }

        /* Allocate new value copy */
        bytes = bhnd_nvram_val_bytes(value, &len, &type);
        error = bhnd_nvram_val_new(&result, value->fmt, bytes, len, type,
            flags);
        if (error) {
                BHND_NV_LOG("copy failed: %d", error);
                return (NULL);
        }

        return (result);
}

/**
 * Release a reference to @p value.
 *
 * If this is the last reference, all associated resources will be freed.
 * 
 * @param       value   The value to be released.
 */
void
bhnd_nvram_val_release(bhnd_nvram_val *value)
{
        BHND_NV_ASSERT(value->refs >= 1, ("value over-released"));

        /* Skip if value is static */
        if (value->val_storage == BHND_NVRAM_VAL_STORAGE_STATIC)
                return;

        /* Drop reference */
        if (!refcount_release(&value->refs))
                return;

        /* Free allocated external representation data */
        switch (value->data_storage) {
        case BHND_NVRAM_VAL_DATA_EXT_ALLOC:
                bhnd_nv_free(__DECONST(void *, value->data.ptr));
                break;
        case BHND_NVRAM_VAL_DATA_NONE:
        case BHND_NVRAM_VAL_DATA_INLINE:
        case BHND_NVRAM_VAL_DATA_EXT_WEAK:
        case BHND_NVRAM_VAL_DATA_EXT_STATIC:
                /* Nothing to free */
                break;
        }

        /* Free instance if dynamically allocated */
        if (value->val_storage == BHND_NVRAM_VAL_STORAGE_DYNAMIC)
                bhnd_nv_free(value);
}

/**
 * Standard BHND_NVRAM_TYPE_NULL encoding implementation.
 */
static int
bhnd_nvram_val_encode_null(const void *inp, size_t ilen, bhnd_nvram_type itype,
    void *outp, size_t *olen, bhnd_nvram_type otype)
{
        size_t  limit, nbytes;

        BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_NULL,
            ("unsupported type: %d", itype));

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

        nbytes = 0;

        /* Write to output */
        switch (otype) {
        case BHND_NVRAM_TYPE_NULL:
                /* Can be directly encoded as a zero-length NULL value */
                nbytes = 0;
                break;
        default:
                /* Not representable */
                return (EFTYPE);
        }

        /* Provide required length */
        *olen = nbytes;
        if (limit < *olen) {
                if (outp == NULL)
                        return (0);

                return (ENOMEM);
        }

        return (0);
}

/**
 * Standard BHND_NVRAM_TYPE_BOOL encoding implementation.
 */
static int
bhnd_nvram_val_encode_bool(const void *inp, size_t ilen, bhnd_nvram_type itype,
    void *outp, size_t *olen, bhnd_nvram_type otype)
{
        bhnd_nvram_bool_t       bval;
        size_t                  limit, nbytes, nelem;
        int                     error;

        BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_BOOL,
            ("unsupported type: %d", itype));

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

        /* Must be exactly one element in input */
        if ((error = bhnd_nvram_value_nelem(inp, ilen, itype, &nelem)))
                return (error);

        if (nelem != 1)
                return (EFTYPE);

        /* Fetch (and normalize) boolean value */
        bval = (*(const bhnd_nvram_bool_t *)inp != 0) ? true : false;

        /* Write to output */
        switch (otype) {
        case BHND_NVRAM_TYPE_NULL:
                /* False can be directly encoded as a zero-length NULL value */
                if (bval != false)
                        return (EFTYPE);

                nbytes = 0;
                break;

        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY: {
                /* Can encode as "true" or "false" */
                const char *str = bval ? "true" : "false";

                nbytes = strlen(str) + 1;
                if (limit > nbytes)
                        strcpy(outp, str);

                break;
        }

        default:
                /* If output type is an integer, we can delegate to standard
                 * integer encoding to encode as zero or one. */
                if (bhnd_nvram_is_int_type(otype)) {
                        uint8_t ival = bval ? 1 : 0;

                        return (bhnd_nvram_val_encode_int(&ival, sizeof(ival),
                            BHND_NVRAM_TYPE_UINT8, outp, olen, otype));
                }

                /* Otherwise not representable */
                return (EFTYPE);
        }

        /* Provide required length */
        *olen = nbytes;
        if (limit < *olen) {
                if (outp == NULL)
                        return (0);

                return (ENOMEM);
        }

        return (0);
}

/**
 * Standard BHND_NVRAM_TYPE_DATA encoding implementation.
 */
static int
bhnd_nvram_val_encode_data(const void *inp, size_t ilen, bhnd_nvram_type itype,
    void *outp, size_t *olen, bhnd_nvram_type otype)
{
        BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_DATA,
            ("unsupported type: %d", itype));

        /* Write to output */
        switch (otype) {
        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY:
                /* If encoding as a string, produce an EFI-style hexadecimal
                 * byte array (HF1F...) by interpreting the octet string
                 * as an array of uint8 values */
                return (bhnd_nvram_value_printf("H%[]02hhX", inp, ilen,
                    BHND_NVRAM_TYPE_UINT8_ARRAY, outp, olen, ""));

        default:
                /* Fall back on direct interpretation as an array of 8-bit
                 * integers array */
                return (bhnd_nvram_value_coerce(inp, ilen,
                    BHND_NVRAM_TYPE_UINT8_ARRAY, outp, olen, otype));
        }
}

/**
 * Standard string/char array/char encoding implementation.
 *
 * Input type must be one of:
 * - BHND_NVRAM_TYPE_STRING
 * - BHND_NVRAM_TYPE_CHAR
 * - BHND_NVRAM_TYPE_CHAR_ARRAY
 */
static int
bhnd_nvram_val_encode_string(const void *inp, size_t ilen,
    bhnd_nvram_type itype, void *outp, size_t *olen, bhnd_nvram_type otype)
{
        const char      *cstr;
        bhnd_nvram_type  otype_base;
        size_t           cstr_size, cstr_len;
        size_t           limit, nbytes;

        BHND_NV_ASSERT(
            itype == BHND_NVRAM_TYPE_STRING ||
            itype == BHND_NVRAM_TYPE_CHAR ||
            itype == BHND_NVRAM_TYPE_CHAR_ARRAY,
            ("unsupported type: %d", itype));

        cstr = inp;
        cstr_size = ilen;
        nbytes = 0;
        otype_base = bhnd_nvram_base_type(otype);

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

        /* Determine string length, minus trailing NUL (if any) */
        cstr_len = strnlen(cstr, cstr_size);

        /* Parse the string data and write to output */
        switch (otype) {
        case BHND_NVRAM_TYPE_NULL:
                /* Only an empty string may be represented as a NULL value */
                if (cstr_len != 0)
                        return (EFTYPE);

                *olen = 0;
                return (0);

        case BHND_NVRAM_TYPE_CHAR:
        case BHND_NVRAM_TYPE_CHAR_ARRAY:
                /* String must contain exactly 1 non-terminating-NUL character
                 * to be represented as a single char */
                if (!bhnd_nvram_is_array_type(otype)) {
                        if (cstr_len != 1)
                                return (EFTYPE);
                }

                /* Copy out the characters directly (excluding trailing NUL) */
                for (size_t i = 0; i < cstr_len; i++) {
                        if (limit > nbytes)
                                *((uint8_t *)outp + nbytes) = cstr[i];
                        nbytes++;
                }

                /* Provide required length */
                *olen = nbytes;
                if (limit < *olen && outp != NULL)
                        return (ENOMEM);

                return (0);

        case BHND_NVRAM_TYPE_BOOL:
        case BHND_NVRAM_TYPE_BOOL_ARRAY: {
                const char              *p;
                size_t                   plen;
                bhnd_nvram_bool_t        bval;

                /* Trim leading/trailing whitespace */
                p = cstr;
                plen = bhnd_nvram_trim_field(&p, cstr_len, '\0');

                /* Parse string representation */
                if (strncasecmp(p, "true", plen) == 0 ||
                    strncasecmp(p, "yes", plen) == 0 ||
                    strncmp(p, "1", plen) == 0)
                {
                        bval = true;
                } else if (strncasecmp(p, "false", plen) == 0 ||
                    strncasecmp(p, "no", plen) == 0 ||
                    strncmp(p, "0", plen) == 0)
                {
                        bval = false;
                } else {
                        /* Not a recognized boolean string */
                        return (EFTYPE);
                }

                /* Write to output */
                nbytes = sizeof(bhnd_nvram_bool_t);
                if (limit >= nbytes)
                        *((bhnd_nvram_bool_t *)outp) = bval;

                /* Provide required length */
                *olen = nbytes;
                if (limit < *olen && outp != NULL)
                        return (ENOMEM);

                return (0);
        }

        case BHND_NVRAM_TYPE_DATA: {
                const char      *p;
                size_t           plen, parsed_len;
                int              error;

                /* Trim leading/trailing whitespace */
                p = cstr;
                plen = bhnd_nvram_trim_field(&p, cstr_len, '\0');

                /* Check for EFI-style hexadecimal byte array string format.
                 * Must have a 'H' prefix  */
                if (plen < 1 || bhnd_nv_toupper(*p) != 'H')
                        return (EFTYPE);

                /* Skip leading 'H' */
                p++;
                plen--;

                /* Parse the input string's two-char octets until the end
                 * of input is reached. The last octet may contain only
                 * one char */
                while (plen > 0) {
                        uint8_t byte;
                        size_t  byte_len = sizeof(byte);

                        /* Parse next two-character hex octet */
                        error = bhnd_nvram_parse_int(p, bhnd_nv_ummin(plen, 2),
                            16, &parsed_len, &byte, &byte_len, otype_base);
                        if (error) {
                                BHND_NV_DEBUG("error parsing '%.*s' as "
                                    "integer: %d\n", BHND_NV_PRINT_WIDTH(plen),
                                     p, error);

                                return (error);
                        }

                        /* Write to output */
                        if (limit > nbytes)
                                *((uint8_t *)outp + nbytes) = byte;
                        nbytes++;

                        /* Advance input */
                        p += parsed_len;
                        plen -= parsed_len;
                }

                /* Provide required length */
                *olen = nbytes;
                if (limit < *olen && outp != NULL)
                        return (ENOMEM);

                return (0);
        }

        case BHND_NVRAM_TYPE_UINT8:
        case BHND_NVRAM_TYPE_UINT8_ARRAY:
        case BHND_NVRAM_TYPE_UINT16:
        case BHND_NVRAM_TYPE_UINT16_ARRAY:
        case BHND_NVRAM_TYPE_UINT32:
        case BHND_NVRAM_TYPE_UINT32_ARRAY:
        case BHND_NVRAM_TYPE_UINT64:
        case BHND_NVRAM_TYPE_UINT64_ARRAY:
        case BHND_NVRAM_TYPE_INT8:
        case BHND_NVRAM_TYPE_INT8_ARRAY:
        case BHND_NVRAM_TYPE_INT16:
        case BHND_NVRAM_TYPE_INT16_ARRAY:
        case BHND_NVRAM_TYPE_INT32:
        case BHND_NVRAM_TYPE_INT32_ARRAY:
        case BHND_NVRAM_TYPE_INT64:
        case BHND_NVRAM_TYPE_INT64_ARRAY: {
                const char      *p;
                size_t           plen, parsed_len;
                int              error;

                /* Trim leading/trailing whitespace */
                p = cstr;
                plen = bhnd_nvram_trim_field(&p, cstr_len, '\0');

                /* Try to parse the integer value */
                error = bhnd_nvram_parse_int(p, plen, 0, &parsed_len, outp,
                    olen, otype_base);
                if (error) {
                        BHND_NV_DEBUG("error parsing '%.*s' as integer: %d\n",
                            BHND_NV_PRINT_WIDTH(plen), p, error);
                        return (error);
                }

                /* Do additional bytes remain unparsed? */
                if (plen != parsed_len) {
                        BHND_NV_DEBUG("error parsing '%.*s' as a single "
                            "integer value; trailing garbage '%.*s'\n",
                            BHND_NV_PRINT_WIDTH(plen), p,
                            BHND_NV_PRINT_WIDTH(plen-parsed_len), p+parsed_len);
                        return (EFTYPE);
                }

                return (0);
        }

        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY:
                /* Copy out the string representation as-is */
                *olen = cstr_size;

                /* Need additional space for trailing NUL? */
                if (cstr_len == cstr_size)
                        (*olen)++;

                /* Skip output? */
                if (outp == NULL)
                        return (0);

                /* Verify required length */
                if (limit < *olen)
                        return (ENOMEM);

                /* Copy and NUL terminate */
                strncpy(outp, cstr, cstr_len);
                *((char *)outp + cstr_len) = '\0';

                return (0);
        }

        BHND_NV_PANIC("unknown type %s", bhnd_nvram_type_name(otype));
}

/**
 * Standard integer encoding implementation.
 */
static int
bhnd_nvram_val_encode_int(const void *inp, size_t ilen, bhnd_nvram_type itype,
    void *outp, size_t *olen, bhnd_nvram_type otype)
{
        bhnd_nvram_type  otype_base;
        size_t           limit, nbytes;
        bool             itype_signed, otype_signed, otype_int;
        union {
                uint64_t        u64;
                int64_t         i64;
        } intv;

        BHND_NV_ASSERT(bhnd_nvram_is_int_type(itype), ("non-integer type"));

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

        /* Fetch output type info */
        otype_base = bhnd_nvram_base_type(otype);
        otype_int = bhnd_nvram_is_int_type(otype);
        otype_signed = bhnd_nvram_is_signed_type(otype_base);

        /*
         * Promote integer value to a common 64-bit representation.
         */
        switch (itype) {
        case BHND_NVRAM_TYPE_UINT8:
                if (ilen != sizeof(uint8_t))
                        return (EFAULT);

                itype_signed = false;
                intv.u64 = *(const uint8_t *)inp;
                break;

        case BHND_NVRAM_TYPE_UINT16:
                if (ilen != sizeof(uint16_t))
                        return (EFAULT);

                itype_signed = false;
                intv.u64 = *(const uint16_t *)inp;
                break;

        case BHND_NVRAM_TYPE_UINT32:
                if (ilen != sizeof(uint32_t))
                        return (EFAULT);

                itype_signed = false;
                intv.u64 = *(const uint32_t *)inp;
                break;

        case BHND_NVRAM_TYPE_UINT64:
                if (ilen != sizeof(uint64_t))
                        return (EFAULT);

                itype_signed = false;
                intv.u64 = *(const uint64_t *)inp;
                break;

        case BHND_NVRAM_TYPE_INT8:
                if (ilen != sizeof(int8_t))
                        return (EFAULT);

                itype_signed = true;
                intv.i64 = *(const int8_t *)inp;
                break;

        case BHND_NVRAM_TYPE_INT16:
                if (ilen != sizeof(int16_t))
                        return (EFAULT);

                itype_signed = true;
                intv.i64 = *(const int16_t *)inp;
                break;

        case BHND_NVRAM_TYPE_INT32:
                if (ilen != sizeof(int32_t))
                        return (EFAULT);

                itype_signed = true;
                intv.i64 = *(const int32_t *)inp;
                break;

        case BHND_NVRAM_TYPE_INT64:
                if (ilen != sizeof(int32_t))
                        return (EFAULT);

                itype_signed = true;
                intv.i64 = *(const int32_t *)inp;
                break;

        default:
                BHND_NV_PANIC("invalid type %d\n", itype);
        }

        /* Perform signed/unsigned conversion */
        if (itype_signed && otype_int && !otype_signed) {
                if (intv.i64 < 0) {
                        /* Can't represent negative value */
                        BHND_NV_LOG("cannot represent %" PRId64 " as %s\n",
                            intv.i64, bhnd_nvram_type_name(otype));

                        return (ERANGE);
                }

                /* Convert to unsigned representation */
                intv.u64 = intv.i64;

        } else if (!itype_signed && otype_int && otype_signed) {
                /* Handle unsigned -> signed coercions */
                if (intv.u64 > INT64_MAX) {
                        /* Can't represent positive value */
                        BHND_NV_LOG("cannot represent %" PRIu64 " as %s\n",
                            intv.u64, bhnd_nvram_type_name(otype));
                        return (ERANGE);
                }

                /* Convert to signed representation */
                intv.i64 = intv.u64;
        }

        /* Write output */
        switch (otype) {
        case BHND_NVRAM_TYPE_NULL:
                /* Cannot encode an integer value as NULL */
                return (EFTYPE);

        case BHND_NVRAM_TYPE_BOOL: {
                bhnd_nvram_bool_t bval;

                if (intv.u64 == 0 || intv.u64 == 1) {
                        bval = intv.u64;
                } else {
                        /* Encoding as a bool would lose information */
                        return (ERANGE);
                }

                nbytes = sizeof(bhnd_nvram_bool_t);
                if (limit >= nbytes)
                        *((bhnd_nvram_bool_t *)outp) = bval;

                break;
        }

        case BHND_NVRAM_TYPE_CHAR:
        case BHND_NVRAM_TYPE_CHAR_ARRAY:
        case BHND_NVRAM_TYPE_DATA:
        case BHND_NVRAM_TYPE_UINT8:
        case BHND_NVRAM_TYPE_UINT8_ARRAY:
                if (intv.u64 > UINT8_MAX)
                        return (ERANGE);

                nbytes = sizeof(uint8_t);
                if (limit >= nbytes)
                        *((uint8_t *)outp) = (uint8_t)intv.u64;
                break;

        case BHND_NVRAM_TYPE_UINT16:
        case BHND_NVRAM_TYPE_UINT16_ARRAY:
                if (intv.u64 > UINT16_MAX)
                        return (ERANGE);

                nbytes = sizeof(uint16_t);
                if (limit >= nbytes)
                        *((uint16_t *)outp) = (uint16_t)intv.u64;
                break;

        case BHND_NVRAM_TYPE_UINT32:
        case BHND_NVRAM_TYPE_UINT32_ARRAY:
                if (intv.u64 > UINT32_MAX)
                        return (ERANGE);

                nbytes = sizeof(uint32_t);
                if (limit >= nbytes)
                        *((uint32_t *)outp) = (uint32_t)intv.u64;
                break;

        case BHND_NVRAM_TYPE_UINT64:
        case BHND_NVRAM_TYPE_UINT64_ARRAY:
                nbytes = sizeof(uint64_t);
                if (limit >= nbytes)
                        *((uint64_t *)outp) = intv.u64;
                break;

        case BHND_NVRAM_TYPE_INT8:
        case BHND_NVRAM_TYPE_INT8_ARRAY:
                if (intv.i64 < INT8_MIN || intv.i64 > INT8_MAX)
                        return (ERANGE);

                nbytes = sizeof(int8_t);
                if (limit >= nbytes)
                        *((int8_t *)outp) = (int8_t)intv.i64;
                break;

        case BHND_NVRAM_TYPE_INT16:
        case BHND_NVRAM_TYPE_INT16_ARRAY:
                if (intv.i64 < INT16_MIN || intv.i64 > INT16_MAX)
                        return (ERANGE);

                nbytes = sizeof(int16_t);
                if (limit >= nbytes)
                        *((int16_t *)outp) = (int16_t)intv.i64;
                break;

        case BHND_NVRAM_TYPE_INT32:
        case BHND_NVRAM_TYPE_INT32_ARRAY:
                if (intv.i64 < INT32_MIN || intv.i64 > INT32_MAX)
                        return (ERANGE);

                nbytes = sizeof(int32_t);
                if (limit >= nbytes)
                        *((int32_t *)outp) = (int32_t)intv.i64;
                break;

        case BHND_NVRAM_TYPE_INT64:
        case BHND_NVRAM_TYPE_INT64_ARRAY:
                nbytes = sizeof(int64_t);
                if (limit >= nbytes)
                        *((int64_t *)outp) = intv.i64;
                break;

        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY: {
                ssize_t len;

                /* Attempt to write the entry + NUL */
                if (otype_signed) {
                        len = snprintf(outp, limit, "%" PRId64, intv.i64);
                } else {
                        len = snprintf(outp, limit, "%" PRIu64, intv.u64);
                }

                if (len < 0) {
                        BHND_NV_LOG("snprintf() failed: %zd\n", len);
                        return (EFTYPE);
                }

                /* Set total length to the formatted string length, plus
                 * trailing NUL */
                nbytes = len + 1;
                break;
        }

        default:
                BHND_NV_LOG("unknown type %s\n", bhnd_nvram_type_name(otype));
                return (EFTYPE);
        }

        /* Provide required length */
        *olen = nbytes;
        if (limit < *olen) {
                if (outp == NULL)
                        return (0);

                return (ENOMEM);
        }

        return (0);
}

/**
 * Encode the given @p value as @p otype, writing the result to @p outp.
 *
 * @param               value   The value to be encoded.
 * @param[out]          outp    On success, the value will be written to this 
 *                              buffer. This argment may be NULL if the value is
 *                              not desired.
 * @param[in,out]       olen    The capacity of @p outp. On success, will be set
 *                              to the actual size of the requested value.
 * @param               otype   The data type to be written to @p outp.
 *
 * @retval 0            success
 * @retval ENOMEM       If the @p outp is non-NULL, and the provided @p olen
 *                      is too small to hold the encoded value.
 * @retval EFTYPE       If value coercion from @p value to @p otype is
 *                      impossible.
 * @retval ERANGE       If value coercion would overflow (or underflow) the
 *                      a @p otype representation.
 */
int
bhnd_nvram_val_encode(bhnd_nvram_val *value, void *outp, size_t *olen,
    bhnd_nvram_type otype)
{
        /* Prefer format implementation */
        if (value->fmt->op_encode != NULL)
                return (value->fmt->op_encode(value, outp, olen, otype));

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

/**
 * Encode the given @p value's element as @p otype, writing the result to
 * @p outp.
 *
 * @param               inp     The element to be encoded. Must be a value
 *                              previously returned by bhnd_nvram_val_next()
 *                              or bhnd_nvram_val_elem().
 * @param               ilen    The size of @p inp, as returned by
 *                              bhnd_nvram_val_next() or bhnd_nvram_val_elem().
 * @param[out]          outp    On success, the value will be written to this 
 *                              buffer. This argment may be NULL if the value is
 *                              not desired.
 * @param[in,out]       olen    The capacity of @p outp. On success, will be set
 *                              to the actual size of the requested value.
 * @param               otype   The data type to be written to @p outp.
 *
 * @retval 0            success
 * @retval ENOMEM       If the @p outp is non-NULL, and the provided @p olen
 *                      is too small to hold the encoded value.
 * @retval EFTYPE       If value coercion from @p value to @p otype is
 *                      impossible.
 * @retval ERANGE       If value coercion would overflow (or underflow) the
 *                      a @p otype representation.
 */
int
bhnd_nvram_val_encode_elem(bhnd_nvram_val *value, const void *inp,
    size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
        /* Prefer format implementation */
        if (value->fmt->op_encode_elem != NULL) {
                return (value->fmt->op_encode_elem(value, inp, ilen, outp,
                    olen, otype));
        }

        return (bhnd_nvram_val_generic_encode_elem(value, inp, ilen, outp,
            olen, otype));
}

/**
 * Return the type, size, and a pointer to the internal representation
 * of @p value.
 * 
 * @param       value   The value to be queried.
 * @param[out]  olen    Size of the returned data, in bytes.
 * @param[out]  otype   Data type.
 */
const void *
bhnd_nvram_val_bytes(bhnd_nvram_val *value, size_t *olen,
    bhnd_nvram_type *otype)
{
        /* Provide type and length */
        *otype = value->data_type;
        *olen = value->data_len;

        switch (value->data_storage) {
        case BHND_NVRAM_VAL_DATA_EXT_ALLOC:
        case BHND_NVRAM_VAL_DATA_EXT_STATIC:
        case BHND_NVRAM_VAL_DATA_EXT_WEAK:
                /* Return a pointer to external storage */
                return (value->data.ptr);

        case BHND_NVRAM_VAL_DATA_INLINE:
                /* Return a pointer to inline storage */
                return (&value->data);

        case BHND_NVRAM_VAL_DATA_NONE:
                BHND_NV_PANIC("uninitialized value");
        }

        BHND_NV_PANIC("unknown storage type: %d", value->data_storage);
}

/**
 * Iterate over all array elements in @p value.
 *
 * @param               value   The value to be iterated
 * @param               prev    A value pointer previously returned by
 *                              bhnd_nvram_val_next() or bhnd_nvram_val_elem(),
 *                              or NULL to begin iteration at the first element.
 * @param[in,out]       olen    If @p prev is non-NULL, @p olen must be a
 *                              pointer to the length previously returned by
 *                              bhnd_nvram_val_next() or bhnd_nvram_val_elem().
 *                              On success, will be set to the next element's
 *                              length, in bytes.
 *
 * @retval non-NULL     A borrowed reference to the element data.
 * @retval NULL         If the end of the element array is reached.
 */
const void *
bhnd_nvram_val_next(bhnd_nvram_val *value, const void *prev, size_t *olen)
{
        /* Prefer the format implementation */
        if (value->fmt->op_next != NULL)
                return (value->fmt->op_next(value, prev, olen));

        return (bhnd_nvram_val_generic_next(value, prev, olen));
}

/**
 * Return the value's data type.
 *
 * @param       value   The value to be queried.
 */
bhnd_nvram_type
bhnd_nvram_val_type(bhnd_nvram_val *value)
{
        return (value->data_type);
}

/**
 * Return value's element data type.
 *
 * @param       value   The value to be queried.
 */
bhnd_nvram_type
bhnd_nvram_val_elem_type(bhnd_nvram_val *value)
{
        return (bhnd_nvram_base_type(value->data_type));
}

/**
 * Return the total number of elements represented by @p value.
 */
size_t
bhnd_nvram_val_nelem(bhnd_nvram_val *value)
{
        const void      *bytes;
        bhnd_nvram_type  type;
        size_t           nelem, len;
        int              error;

        /* Prefer format implementation */
        if (value->fmt->op_nelem != NULL)
                return (value->fmt->op_nelem(value));

        /*
         * If a custom op_next() is defined, bhnd_nvram_value_nelem() almost
         * certainly cannot produce a valid element count; it assumes a standard
         * data format that may not apply when custom iteration is required.
         *
         * Instead, use bhnd_nvram_val_next() to parse the backing data and
         * produce a total count.
         */
        if (value->fmt->op_next != NULL) {
                const void *next;

                next = NULL;
                nelem = 0;
                while ((next = bhnd_nvram_val_next(value, next, &len)) != NULL)
                        nelem++;

                return (nelem);
        }

        /* Otherwise, compute the standard element count */
        bytes = bhnd_nvram_val_bytes(value, &len, &type);
        if ((error = bhnd_nvram_value_nelem(bytes, len, type, &nelem))) {
                /* Should always succeed */
                BHND_NV_PANIC("error calculating element count for type '%s' "
                    "with length %zu: %d\n", bhnd_nvram_type_name(type), len,
                    error);
        }

        return (nelem);
}

/**
 * Generic implementation of bhnd_nvram_val_op_encode(), compatible with
 * all supported NVRAM data types.
 */
int
bhnd_nvram_val_generic_encode(bhnd_nvram_val *value, void *outp, size_t *olen,
    bhnd_nvram_type otype)
{
        const void      *inp;
        bhnd_nvram_type  itype;
        size_t           ilen;
        const void      *next;
        bhnd_nvram_type  otype_base;
        size_t           limit, nelem, nbytes;
        size_t           next_len;
        int              error;

        nbytes = 0;
        nelem = 0;
        otype_base = bhnd_nvram_base_type(otype);
        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);

        /*
         * Normally, an array type is not universally representable as
         * non-array type.
         * 
         * As exceptions, we support conversion directly to/from:
         *      - CHAR_ARRAY/STRING:
         *              ->STRING        Interpret the character array as a
         *                              non-NUL-terminated string.
         *              ->CHAR_ARRAY    Trim the trailing NUL from the string.
         */
#define BHND_NV_IS_ISO_CONV(_lhs, _rhs)         \
        ((itype == BHND_NVRAM_TYPE_ ## _lhs &&  \
          otype == BHND_NVRAM_TYPE_ ## _rhs) || \
         (itype == BHND_NVRAM_TYPE_ ## _rhs &&  \
          otype == BHND_NVRAM_TYPE_ ## _lhs))

        if (BHND_NV_IS_ISO_CONV(CHAR_ARRAY, STRING)) {
                return (bhnd_nvram_val_encode_elem(value, inp, ilen, outp, olen,
                    otype));
        }

#undef  BHND_NV_IS_ISO_CONV

        /*
         * If both input and output are non-array types, try to encode them
         * without performing element iteration.
         */
        if (!bhnd_nvram_is_array_type(itype) &&
            !bhnd_nvram_is_array_type(otype))
        {
                return (bhnd_nvram_val_encode_elem(value, inp, ilen, outp, olen,
                    otype));
        }

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

        /* Iterate over our array elements and encode as the requested
         * type */
        next = NULL;
        while ((next = bhnd_nvram_val_next(value, next, &next_len))) {
                void                    *elem_outp;
                size_t                   elem_nbytes;

                /* If the output type is not an array type, we can only encode
                 * one element */
                nelem++;
                if (nelem > 1 && !bhnd_nvram_is_array_type(otype)) {
                        return (EFTYPE);
                }

                /* Determine output offset / limit */
                if (nbytes >= limit) {
                        elem_nbytes = 0;
                        elem_outp = NULL;
                } else {
                        elem_nbytes = limit - nbytes;
                        elem_outp = (uint8_t *)outp + nbytes;
                }

                /* Attempt encode */
                error = bhnd_nvram_val_encode_elem(value, next, next_len,
                    elem_outp, &elem_nbytes, otype_base);

                /* If encoding failed for any reason other than ENOMEM (which
                 * we'll detect and report below), return immediately */
                if (error && error != ENOMEM)
                        return (error);

                /* Add to total length */
                if (SIZE_MAX - nbytes < elem_nbytes)
                        return (EFTYPE); /* would overflow size_t */

                nbytes += elem_nbytes;
        }

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

        /* If no output was requested, nothing left to do */
        if (outp == NULL)
                return (0);

        /* Otherwise, report a memory error if the output buffer was too
         * small */
        if (limit < nbytes)
                return (ENOMEM);

        return (0);
}

/**
 * Generic implementation of bhnd_nvram_val_op_encode_elem(), compatible with
 * all supported NVRAM data types.
 */
int
bhnd_nvram_val_generic_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;

        itype = bhnd_nvram_val_elem_type(value);
        switch (itype) {
        case BHND_NVRAM_TYPE_NULL:
                return (bhnd_nvram_val_encode_null(inp, ilen, itype, outp, olen,
                    otype));

        case BHND_NVRAM_TYPE_DATA:
                return (bhnd_nvram_val_encode_data(inp, ilen, itype, outp,
                    olen, otype));

        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_CHAR:
                return (bhnd_nvram_val_encode_string(inp, ilen, itype, outp,
                    olen, otype));

        case BHND_NVRAM_TYPE_BOOL:
                return (bhnd_nvram_val_encode_bool(inp, ilen, itype, outp, olen,
                    otype));

        case BHND_NVRAM_TYPE_UINT8:
        case BHND_NVRAM_TYPE_UINT16:
        case BHND_NVRAM_TYPE_UINT32:
        case BHND_NVRAM_TYPE_UINT64:
        case BHND_NVRAM_TYPE_INT8:
        case BHND_NVRAM_TYPE_INT16:
        case BHND_NVRAM_TYPE_INT32:
        case BHND_NVRAM_TYPE_INT64:
                return (bhnd_nvram_val_encode_int(inp, ilen, itype, outp, olen,
                    otype));    
        default:
                BHND_NV_PANIC("missing encode_elem() implementation");
        }
}

/**
 * Generic implementation of bhnd_nvram_val_op_next(), compatible with
 * all supported NVRAM data types.
 */
const void *
bhnd_nvram_val_generic_next(bhnd_nvram_val *value, const void *prev,
    size_t *olen)
{
        const uint8_t   *inp;
        bhnd_nvram_type  itype;
        size_t           ilen;

        /* Iterate over the backing representation */
        inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
        return (bhnd_nvram_value_array_next(inp, ilen, itype, prev, olen));
}

/**
 * Initialize the representation of @p value with @p ptr.
 *
 * @param       value   The value to be initialized.
 * @param       inp     The external representation.
 * @param       ilen    The external representation length, in bytes.
 * @param       itype   The external representation's data type.
 * @param       flags   Value flags.
 * 
 * @retval 0            success.
 * @retval ENOMEM       if allocation fails
 * @retval EFTYPE       if @p itype is not an array type, and @p ilen is not
 *                      equal to the size of a single element of @p itype.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 */
static int
bhnd_nvram_val_set(bhnd_nvram_val *value, const void *inp, size_t ilen,
    bhnd_nvram_type itype, uint32_t flags)
{
        void    *bytes;
        int      error;

        BHND_NVRAM_VAL_ASSERT_EMPTY(value);

        /* Validate alignment */
        if ((error = bhnd_nvram_value_check_aligned(inp, ilen, itype)))
                return (error);

        /* Reference the external data */
        if ((flags & BHND_NVRAM_VAL_BORROW_DATA) ||
            (flags & BHND_NVRAM_VAL_STATIC_DATA))
        {
                if (flags & BHND_NVRAM_VAL_STATIC_DATA)
                        value->data_storage = BHND_NVRAM_VAL_DATA_EXT_STATIC;
                else
                        value->data_storage = BHND_NVRAM_VAL_DATA_EXT_WEAK;

                value->data.ptr = inp;
                value->data_type = itype;
                value->data_len = ilen;
                return (0);
        }

        /* Fetch reference to (or allocate) an appropriately sized buffer */
        bytes = bhnd_nvram_val_alloc_bytes(value, ilen, itype, flags);
        if (bytes == NULL)
                return (ENOMEM);

        /* Copy data */
        memcpy(bytes, inp, ilen);

        return (0);
}

/**
 * Initialize the internal inline representation of @p value with a copy of
 * the data referenced by @p inp of @p itype.
 * 
 * If @p inp is NULL, @p itype and @p ilen will be validated, but no data will
 * be copied.
 *
 * @param       value   The value to be initialized.
 * @param       inp     The input data to be copied, or NULL to verify
 *                      that data of @p ilen and @p itype can be represented
 *                      inline.
 * @param       ilen    The size of the external buffer to be allocated.
 * @param       itype   The type of the external buffer to be allocated.
 * 
 * @retval 0            success
 * @retval ENOMEM       if @p ilen is too large to be represented inline.
 * @retval EFAULT       if @p ilen is not correctly aligned for elements of
 *                      @p itype.
 */
static int
bhnd_nvram_val_set_inline(bhnd_nvram_val *value, const void *inp, size_t ilen,
    bhnd_nvram_type itype)
{
        BHND_NVRAM_VAL_ASSERT_EMPTY(value);

#define NV_STORE_INIT_INLINE()  do {                                    \
        value->data_len = ilen;                                         \
        value->data_type = itype;                                       \
} while(0)

#define NV_STORE_INLINE(_type, _dest)   do {                            \
        if (ilen != sizeof(_type))                                      \
                return (EFAULT);                                        \
                                                                        \
        if (inp != NULL) {                                              \
                value->data._dest[0] = *(const _type *)inp;             \
                NV_STORE_INIT_INLINE();                                 \
        }                                                               \
} while (0)

#define NV_COPY_ARRRAY_INLINE(_type, _dest)     do {            \
        if (ilen % sizeof(_type) != 0)                          \
                return (EFAULT);                                \
                                                                \
        if (ilen > nitems(value->data. _dest))                  \
                return (ENOMEM);                                \
                                                                \
        if (inp == NULL)                                        \
                return (0);                                     \
                                                                \
        memcpy(&value->data._dest, inp, ilen);                  \
        if (inp != NULL) {                                      \
                memcpy(&value->data._dest, inp, ilen);          \
                NV_STORE_INIT_INLINE();                         \
        }                                                       \
} while (0)

        /* Attempt to copy to inline storage */
        switch (itype) {
        case BHND_NVRAM_TYPE_NULL:
                if (ilen != 0)
                        return (EFAULT);

                /* Nothing to copy */
                NV_STORE_INIT_INLINE();
                return (0);

        case BHND_NVRAM_TYPE_CHAR:
                NV_STORE_INLINE(uint8_t, ch);
                return (0);

        case BHND_NVRAM_TYPE_BOOL:
                NV_STORE_INLINE(bhnd_nvram_bool_t, b);
                return(0);

        case BHND_NVRAM_TYPE_UINT8:
        case BHND_NVRAM_TYPE_INT8:
                NV_STORE_INLINE(uint8_t, u8);
                return (0);

        case BHND_NVRAM_TYPE_UINT16:
        case BHND_NVRAM_TYPE_INT16:
                NV_STORE_INLINE(uint16_t, u16);
                return (0);

        case BHND_NVRAM_TYPE_UINT32:
        case BHND_NVRAM_TYPE_INT32:
                NV_STORE_INLINE(uint32_t, u32);
                return (0);

        case BHND_NVRAM_TYPE_UINT64:
        case BHND_NVRAM_TYPE_INT64:
                NV_STORE_INLINE(uint32_t, u32);
                return (0);

        case BHND_NVRAM_TYPE_CHAR_ARRAY:
                NV_COPY_ARRRAY_INLINE(uint8_t, ch);
                return (0);

        case BHND_NVRAM_TYPE_DATA:
        case BHND_NVRAM_TYPE_UINT8_ARRAY:
        case BHND_NVRAM_TYPE_INT8_ARRAY:
                NV_COPY_ARRRAY_INLINE(uint8_t, u8);
                return (0);

        case BHND_NVRAM_TYPE_UINT16_ARRAY:
        case BHND_NVRAM_TYPE_INT16_ARRAY:
                NV_COPY_ARRRAY_INLINE(uint16_t, u16);
                return (0);

        case BHND_NVRAM_TYPE_UINT32_ARRAY:
        case BHND_NVRAM_TYPE_INT32_ARRAY:
                NV_COPY_ARRRAY_INLINE(uint32_t, u32);
                return (0);

        case BHND_NVRAM_TYPE_UINT64_ARRAY:
        case BHND_NVRAM_TYPE_INT64_ARRAY:
                NV_COPY_ARRRAY_INLINE(uint64_t, u64);
                return (0);

        case BHND_NVRAM_TYPE_BOOL_ARRAY:
                NV_COPY_ARRRAY_INLINE(bhnd_nvram_bool_t, b);
                return(0);

        case BHND_NVRAM_TYPE_STRING:
        case BHND_NVRAM_TYPE_STRING_ARRAY:
                if (ilen > sizeof(value->data.ch))
                        return (ENOMEM);

                if (inp != NULL) {
                        memcpy(&value->data.ch, inp, ilen);
                        NV_STORE_INIT_INLINE();
                }

                return (0);
        }

#undef  NV_STORE_INIT_INLINE
#undef  NV_STORE_INLINE
#undef  NV_COPY_ARRRAY_INLINE

        BHND_NV_PANIC("unknown data type %d", itype);
}

/**
 * Initialize the internal representation of @p value with a buffer allocation
 * of @p len and @p itype, returning a pointer to the allocated buffer.
 * 
 * If a buffer of @p len and @p itype can be represented inline, no
 * external buffer will be allocated, and instead a pointer to the inline
 * data representation will be returned.
 *
 * @param       value   The value to be initialized.
 * @param       ilen    The size of the external buffer to be allocated.
 * @param       itype   The type of the external buffer to be allocated.
 * @param       flags   Value flags.
 * 
 * @retval non-null     The newly allocated buffer.
 * @retval NULL         If allocation failed.
 * @retval NULL         If @p value is an externally allocated instance.
 */
static void *
bhnd_nvram_val_alloc_bytes(bhnd_nvram_val *value, size_t ilen,
    bhnd_nvram_type itype, uint32_t flags)
{
        void *ptr;

        BHND_NVRAM_VAL_ASSERT_EMPTY(value);

        /* Can we use inline storage? */
        if (bhnd_nvram_val_set_inline(value, NULL, ilen, itype) == 0) {
                BHND_NV_ASSERT(sizeof(value->data) >= ilen,
                    ("ilen exceeds inline storage"));

                value->data_type = itype;
                value->data_len = ilen;
                value->data_storage = BHND_NVRAM_VAL_DATA_INLINE;
                return (&value->data);
        }

        /* Is allocation permitted? */
        if (!(flags & BHND_NVRAM_VAL_DYNAMIC))
                return (NULL);

        /* Allocate external storage */
        if ((ptr = bhnd_nv_malloc(ilen)) == NULL)
                return (NULL);

        value->data.ptr = ptr;
        value->data_len = ilen;
        value->data_type = itype;
        value->data_storage = BHND_NVRAM_VAL_DATA_EXT_ALLOC;

        return (ptr);
}