User Defined Data Types are supported with callback functions that apply to all YANG leaf or leaf-list nodes using a specific data type.

There are 3 types of callbacks supported to allow customization of YANG data types:

  • validate: custom validation function, used when a data node is set

  • canonical: convert a value to canonical format, used when a data node is set

  • compare: compare 2 values of the same data type, used when a data node is set


There are several standard YANG data types that are supported with built-in canonical functions, which can be found in ncx/ipaddr_typ.c (if sources are available). The server will automatically check for validate and canonical callbacks when a data node is parsed from XML or JSON input. The automatic canonical check can be disabled using the with-canonical” CLI parameter.


      leaf with-canonical {
        description
          "If set to 'true', then the server will automatically
           convert XML and JSON input parameters to the canonical
           format for the YANG data type, if possible.

           The following built-in YANG data types are affected:
             - ipv6-address
             - ipv6-address-no-zone
             - domain-name
             - phys-address
             - mac-address
             - hex-string
             - uuid

           Any canonical callbacks for user-defined data types
           are also affected by this parameter.

           Internal values can be manually converted to canonical
           format using the val_set_canonical API.
          ";
        type boolean;
        default true;
      }


Callback Registration


A user defined type is registered with the function typ_userdef_register. This must be done after the YANG module containing the typedef statement has been loaded. Only top-level typedefs can be supported. Typedefs within groupings or nested within containers or lists cannot be supported.



/********************************************************************
* FUNCTION typ_userdef_register
*
* Register the callback functions for a user defined type
* At least one callback needs to be set in order for this
* userdef type to have any affect
*
* INPUTS:
*   modname == module name defining the type
*   typname == name of the type
*   validate_fn == validate callback (may be NULL)
*   canonical_fn == canonical callback (may be NULL)
*   compare_fn == compare callback (may be NULL)
*   cookie == cookie to pass to callback function
* RETURNS:
*    status
*********************************************************************/
extern status_t
    typ_userdef_register (const xmlChar *modname,
                          const xmlChar *typname,
                          typ_validate_fn_t validate_fn,
                          typ_canonical_fn_t canonical_fn,
                          typ_compare_fn_t compare_fn,
                          void *cookie);


Parameters:

  • modname: module name containing the typedef (mandatory)

  • typname: type name (mandatory)

  • validate_fn: validation callback (optional)

  • canonical_fn: canonical callback (optional)

  • compare_fn: compare callback (optional)

  • cookie: pointer that will be passed to callback (optional)


Example:


 /* ietf-inet-types:ipv6-address */
    status_t res =
        typ_userdef_register((const xmlChar *)”ietf-inet-types”,
                             (const xmlChar *)"ipv6-address",
                             NULL,   // validate_fn
                             ipv6_address_canonical_fn,
                             NULL,   // compare_fn
                             NULL);  // cookie
    if (res != NO_ERR) {
        return res;
    }


typ_validate_fn_t


The validate callback is used for special custom validation of values conforming to a YANG data type.

This function returns NO_ERR even if it does not do any validation.

It returns an error code if validation fails.


/* userdef validate callback function
 *
 * user validation callback for a userdef type
 * INPUTS:
 *   typdef == type definition for the user defined type
 *   inval == input value to convert
 *   cookie == cookie value passed to register function
 * RETURNS:
 *   the validation status
 */
typedef status_t
    (*typ_validate_fn_t) (typ_def_t *typdef,
                          val_value_t *val,
                          void *cookie);


Parameters:

  • typdef: internal type definition control block for the data type being validated

  • val: value structure containing the value to be validated

  • cookie: cookie pointer passed to registration function



Example:


/********************************************************************
* FUNCTION admin_validate_fn
*
* validate callback for an administrative string
*
* INPUTS:
*   typdef == type definition for the user defined type
*   val == input value to validate
*   cookie == cookie value passed to register function
* RETURNS:
*   status
*/
static status_t
    admin_validate_fn (typ_def_t *typdef,
                       val_value_t *val,
                       void *cookie)
{
    (void)typdef;
    (void)cookie;

    if (VAL_TYPE(val) != NCX_BT_STRING) {
        return ERR_NCX_OPERATION_FAILED;
    }

    const xmlChar *valstr = val->v.str;
    if (valstr == NULL) {
        return NO_ERR;
    }

    /* check if the string passes admin format checks */
    if (check_admin_string_ok(valstr)) {
       return NO_ERR;
    }
    return ERR_NCX_INVALID_VALUE;
   

} /* admin_validate_fn */



typ_canonical_fn_t


The canonical callback is used to convert a data node value to the canonical format for the data type. This is important for list key leafs, or else different representations of the same value (e.g. ipv6-address) will be treated as separate list entries.


This function returns NO_ERR even if it does not do any conversion to canonical format.

It returns an error code if some error occurs and conversion to canonical format fails.


/* userdef canonical callback function
 *
 * convert the inval to the canonical format for the type
 * INPUTS:
 *   typdef == type definition for the user defined type
 *   val == input value to convert
 *   cookie == cookie value passed to register function
 * OUTPUTS:
 *   val == can be converted to canonical format
 * RETURNS:
 *   status
 */
typedef status_t
    (*typ_canonical_fn_t) (typ_def_t *typdef,
                           val_value_t *val,
                           void *cookie);


Parameters:

  • typdef: internal type definition control block for the data type being converted

  • val: value structure containing the value to be converted in place to canonical format

  • cookie: cookie pointer passed to registration function



Example:


/********************************************************************
* FUNCTION lowercase_canonical_fn
*
* <generic convert to lowercase>
* canonical callback for a domain name string
* convert the inval to the canonical format for the type
*
* INPUTS:
*   typdef == type definition for the user defined type
*   val == input value to convert
*   cookie == cookie value passed to register function
* OUTPUTS:
*   val == can be changed to canonical format
* RETURNS:
*   status
*/
static status_t
    lowercase_canonical_fn (typ_def_t *typdef,
                            val_value_t *val,
                            void *cookie)
{
    (void)typdef;
    (void)cookie;

    if (VAL_TYPE(val) != NCX_BT_STRING) {
        return ERR_NCX_OPERATION_FAILED;
    }

    xmlChar *valstr = VAL_STRING(val);
    if (valstr == NULL) {
        return NO_ERR;
    }

    /* convert the string to lowercase in place */
    xmlChar *p = valstr;
    while (*p) {
        *p = (xmlChar)tolower((int)*p);
    }

    return NO_ERR;

} /* lowercase_canonical_fn */


typ_compare_fn_t



The compare callback is used to compare two values of the same data type. It is used in the val_compare functions. The standard compare function will skipped if this callback is present for the data type. This function is only needed if the standard compare function for the data type is not correct for some reason (E.g: a vendor-specific structured string).


This function returns NO_ERR if the comparison is done and no need to do the standard compare.

This function returns ERR_NCX_SKIPPED if the comparison is not done so the standard compare function needs to be invoked.


This function returns some error code if an error occurs while attempting the comparison (e.g. ERR_INTERNAL_MEM if a malloc fails)



/* userdef compare callback function
 *
 * compare 2 val_value_t nodes of the same user defined type
 * INPUTS:
 *   typdef == type definition for the user defined type
 *   val1 == input value 1 to comapre
 *   val2 == input value 2 to comapre
 *   cookie == cookie value passed to register function
 *   retval == address of return compare value
 * OUTPUTS:
 *   *retval == return compare value
 * RETURNS:
 *   status (ERR_NCX_SKIPPED if no compare done)
 */
typedef status_t
    (*typ_compare_fn_t) (const typ_def_t *typdef,
                         const val_value_t *val1,
                         const val_value_t *val2,
                         void *cookie,
                         int *retval);


Parameters:

  • typdef: internal type definition control block for the data type being compared

  • val1:one of the values to compare

  • val2:one of the values to compare

  • cookie: cookie pointer passed to registration function

  • retval: pointer to compare result value (set to -1, 0, or 1) if return value is NO_ERR



Example:


* compare 2 val_value_t nodes of the same user defined type
 * INPUTS:
 *   typdef == type definition for the user defined type
 *   val1 == input value 1 to comapre
 *   val2 == input value 2 to comapre
 *   cookie == cookie value passed to register function
 *   retval == address of return compare value
 * OUTPUTS:
 *   *retval == return compare value
 * RETURNS:
 *   status (ERR_NCX_SKIPPED if no compare done)
 */
static status_t
    admin_compare_fn (typ_def_t *typdef,
                      const val_value_t *val1,
                      const val_value_t *val2,
                      void *cookie,
                      int *retval)
{
    (void)typdef;
    (void)cookie;

    if (VAL_TYPE(val1) != NCX_BT_STRING) {
        return ERR_NCX_OPERATION_FAILED;
    }

    if (VAL_TYPE(val12) != NCX_BT_STRING) {
        return ERR_NCX_OPERATION_FAILED;
    }

    const xmlChar *val1str = VAL_STRING(val1);
    if (val1str == NULL) {
        return ERR_NCX_SKIPPED;
    }

    const xmlChar *val2str = VAL_STRING(val2);
    if (val2str == NULL) {
        return ERR_NCX_SKIPPED;
    }

    /* compare as case-insensitive strings */
    int ret = strcasecmp(const char *)val1str, (const char *)val2str);
    *retval = ret;
    return NO_ERR;

} /* admin_compare_fn */