Example of a GET2 callback for a list with multiple key leafs

  • GET2 callbacks support the following protocol operations
    • NETCONF <get>
    • NETCONF <get-data>
    • NETCONF <get-bulk>
    • RESTCONF GET


GET-NEXT Processing can be complex for such a list.


  • GET-NEXT processing is required by the GET2 callback function for a YANG list.
  • The GET2 callback is given a 3-tuple for each key
    • key_value: only valid if key_present is true
    • key_present:: true if the key_value is valid
    • key_fixed: true if the key_value is a fixed value. Do not change this key value when looking for a suitable next!
  • The GET2 callback can support several use-cases with the same code
    • GET-EXACT (all keys provided and callback mode is GET)
    • GET-FIRST (no keys provided and callback mode is GET)
    • GET-FIRST-FIXED (partial keys provided and callback mode is GET)
    • GET-NEXT (all keys provided and callback mode is GETNEXT)
  • For all modes except GET-EXACT, multiple entries can be returned if "getbulk mode" is used
    • The example shows a loop that is executed until entries_done reached max_entries or no more list entries are available
  • The match test is optional for the GET2 callback to implement
    • A content-match node can be specified in a subtree or XPath filterby the client
    • A content-match test will only be present for non-key leaf or leaf-list nodes


GET2 Callback Steps

Step 1) Figure out the next entry based on the keys provided in the request

Step 2) Get the next entry from the system

Step 3) Check the next entry with any content-match tests and go back to Step 1 if any tests fail

Step 4) Setup the return keys in the getcb strcture

Step 5) Add any non-key leafs to the getcb unless "keys only" mode is selected

Step 6) Finish the getbulk entry

Step 7) Check if any more bulk entries and if so, go to Step 1


GET2 Callback Example Module


module t60 {
    yang-version 1.1;
    namespace "http://netconfcentral.org/ns/t60";
    prefix "t";
    revision 2021-08-08;

    container top {
      config false;
      list A {
         key "a b c";
         leaf a { type uint8;  }
         leaf b { type uint8;  }
         leaf c { type uint8;  }
         leaf d { type string;  }
      }
    }

}


GET2 Example GET-NEXT Function (from u_t60.c)


/* put your static variables here */
#define NO_ENTRY 0
#define FIRST_ENTRY 1
#define LAST_ENTRY 3

/**
 * @brief Get the next key tuple for an entry to return
 *
 * In this example there are 3 keys all with values
 * from 1 to 3.
 *
 * In reality the client may send a request for a
 * non-existent entry (a hole) instead of the previous
 * entry. This example uses a simple dense value set so
 * there are no holes in the possible tuples that can be
 * provided for processing.
 *
 * The GET-NEXT algorithm works like an odometer,
 * where each key leaf represents a digit.
 * If a major index increments, then all the indices
 * after it start over (at 1 in this example), unless
 * they are fixed key leafs. A fixed key comes from
 * a subtree or XPath filter that specifies some but
 * not all of the key values.  The GET2 function is
 * expected to check for fixed keys and iterate through
 * the entires only incrementing the non-fixed keys.
 *
 * A real getnext function would probably return
 * a pointer to the internal data structure found.
 * This example is just returning the key values.
 *
 * @param getnext true to getnext; false for get exact
 * Parameters from the u_ GET2 callback.\n
 * If getnext=true then the next entry is requested.\n
 * If getnext=false then check the number of keys
 * provided. If all keys are provided, then this is a
 * real GET-EXACT request. If zero or partial keys
 * are provided, then this is a GET-NEXT request.
 * @param lastentry address of lastentry return flag
 * @param k_t_a pointer to first key 'a' and also return 'a'
 * @param a_fixed == true if 'a' is fixed key
 * @param a_present == true if 'a' is present (could also check
 *    if k_t_a is NULL)
 * @param k_t_b pointer to second key 'b' and also return 'b'
 * @param b_fixed == true if 'b' is fixed key
 * @param b_present == true if 'b' is present (could also check
 *    if k_t_b is NULL)
 * @param k_t_c pointer to third key 'c' and also return 'c'
 * @param c_fixed == true if 'c' is fixed key
 * @param c_present == true if 'c' is present (could also check
 *     if k_t_c is NULL)
 * @retval *k_t_a found key 'a' if return true
 * @retval *k_t_b found key 'b' if return true
 * @retval *k_t_c found key 'c' if return true
 * @retval *lastentry true if there are no more entries
 *    matching the filters.\n
 *    false if there might be more entries left
 * @return true if next is being returned\n
 *  false if ERR_NCX_NO_INSTANCE
 */
static boolean getnext_A (boolean getnext,
                          boolean *lastentry,
                          uint8 *k_t_a,
                          boolean a_fixed,
                          boolean a_present,
                          uint8 *k_t_b,
                          boolean b_fixed,
                          boolean b_present,
                          uint8 *k_t_c,
                          boolean c_fixed,
                          boolean c_present)
{

    *lastentry = false;
    if (!getnext) {
        /* the first getnext is really a GET with either no keys
         * or 1 or more fixed keys.
         */
        if (!a_present && !b_present && !c_present) {
            if (LOGDEBUG2) {
                log_debug2("\ngetnext_A: got first tuple [%u, %u, %u]",
                           FIRST_ENTRY, FIRST_ENTRY, FIRST_ENTRY);
            }

            /* get first entry */
            *k_t_a = FIRST_ENTRY;
            *k_t_b = FIRST_ENTRY;
            *k_t_c = FIRST_ENTRY;
            return true;
        } /* else at least one key is present; keep looking  */
    }

    /* check corner-case where all keys are fixed
     * in that case no point looking for a next
     */
    if (a_fixed && b_fixed && c_fixed) {
        /* If first or real GET then all fixed is OK
         * If all keys fixed for GETNEXT then no entry possible
         */
        return !getnext;
    }

    /* using the key leafs to indicate no value.
     * the 'foo_prenset' flags could also be used
     * in case the value does not have any unused values
     */
    uint8 a = NO_ENTRY;
    uint8 b = NO_ENTRY;
    uint8 c = NO_ENTRY;

    /* Get the starting set of keys to get the next for
     * If the key is present, check the value range now
     */
    if (a_present) {
        a = *k_t_a;
        if ((a == NO_ENTRY) || (a > LAST_ENTRY)) {
            return false;
        }
    }
    if (b_present) {
        b = *k_t_b;
        if ((b == NO_ENTRY) || (b > LAST_ENTRY)) {
            return false;
        }
    }
    if (c_present) {
        c = *k_t_c;
        if ((c == NO_ENTRY) || (c > LAST_ENTRY)) {
            return false;
        }
    }

    /* check GET-EXACT corner-case */
    if (!getnext && (a_present && b_present && c_present)) {
        /* got all keys for a GET so return OK */
        if (LOGDEBUG2) {
            log_debug2("\ngetnext_A: GET exact tuple [%u, %u, %u]",
                       a, b, c);
        }
        *lastentry = true;
        return true;
    }

    /* alway increment or reset 'c'
     * use flags for 'a' and 'b' to iterate through the
     * loop and start on the key that is changing.
     * If next 'c' not found then retry starting with same a and inc_b
     * If next 'b' not found then retry starting with inc_a and reset b,c
     */
    boolean inc_a = false;
    boolean inc_b = false;

    /** find the next, given the current keys */
    while (1) {
        if ((a == NO_ENTRY) || inc_a) {
            if (a_fixed || (a >= LAST_ENTRY)) {
                return false;
            }
            a++;

            if (!b_fixed) {
                b = FIRST_ENTRY;
            }
            if (!c_fixed) {
                c = FIRST_ENTRY;
            }

            /* return this tuple within range */
            break;
        }

        if ((b == NO_ENTRY) || inc_b) {
            if (b_fixed || (b >= LAST_ENTRY)) {
                if (c_fixed || (c >= LAST_ENTRY)) {
                    inc_a = true;
                    inc_b = false;
                    continue;
                } else {
                    c++;
                    break;
                }
            } else {
                b++;
                if (!c_fixed) {
                    c = FIRST_ENTRY;
                }
                break;
            }
        }

        if (c_fixed || (c >= LAST_ENTRY)) {
            inc_a = false;
            inc_b = true;
            continue;
        } else if (c == NO_ENTRY) {
            c = FIRST_ENTRY;
        } else {
            c++;
        }
        break;
    }

    if (LOGDEBUG2) {
        log_debug2("\ngetnext_A: got tuple [%u, %u, %u]", a, b, c);
    }

    *k_t_a = a;
    *k_t_b = b;
    *k_t_c = c;

    /* this may not be easy to tell for a real data structure
     * in that case set *lastentry to false if unknown.
     * The server will retry and the next call can return NO_INSTANCE
     * Set *lastentry to true will cause the server to stop checking
     * for more 'A' list entries
     */
    if ((a == LAST_ENTRY) && (b == LAST_ENTRY) && (c == LAST_ENTRY)) {
        *lastentry = true;
    }

    return true;

} /* getnext_A */


Example GET2 Callback for a YANG List (from u_t60.c)


/**
 * @brief Get database object callback for list A (getcb_fn2_t)\n
 * Path: list /top/A\n
 *
 * Fill in 'get2cb' response fields.
 *
 * @param get2cb GET2 control block for the callback.
 * @param k_t_a Local key leaf 'a' in list 'A'\n
 * Path: /t:top/t:A/t:a
 * @param a_fixed TRUE if this key is fixed in a getnext request.
 * @param a_present TRUE if this key is present and 'k_t_a' is valid.\n
 * FALSE to get first in a getnext request.
 * @param k_t_b Local key leaf 'b' in list 'A'\n
 * Path: /t:top/t:A/t:b
 * @param b_fixed TRUE if this key is fixed in a getnext request.
 * @param b_present TRUE if this key is present and 'k_t_b' is valid.\n
 * FALSE to get first in a getnext request.
 * @param k_t_c Local key leaf 'c' in list 'A'\n
 * Path: /t:top/t:A/t:c
 * @param c_fixed TRUE if this key is fixed in a getnext request.
 * @param c_present TRUE if this key is present and 'k_t_c' is valid.\n
 * FALSE to get first in a getnext request.
 * @return return status of the callback.
 */
status_t u_t_A_get (
    getcb_get2_t *get2cb,
    uint8 k_t_a,
    boolean a_fixed,
    boolean a_present,
    uint8 k_t_b,
    boolean b_fixed,
    boolean b_present,
    uint8 k_t_c,
    boolean c_fixed,
    boolean c_present)
{

    if (LOGDEBUG) {
        log_debug("\nEnter u_t_A_get");
    }

    boolean getnext = FALSE;

    /* check the callback mode type */
    getcb_mode_t cbmode = GETCB_GET2_CBMODE(get2cb);
    switch (cbmode) {
    case GETCB_GET_VALUE:
        break;
    case GETCB_GETNEXT_VALUE:
        getnext = TRUE;
        break;
    default:
        /* USE SET_ERROR FOR PROGRAMMING BUGS ONLY */
        return SET_ERROR(ERR_INTERNAL_VAL);
    }

    obj_template_t *obj = GETCB_GET2_OBJ(get2cb);
    status_t res = NO_ERR;

    uint32 entries_done = 0;
    uint32 max_entries = GETCB_GET2_MAX_ENTRIES(get2cb);
    if (max_entries == 0) {
        max_entries = 6;
    }

    boolean done = false;
    while (!done) {

        /* For GET, find the entry that matches the key values
         * For GETNEXT, find the entry that matches the next key value
         * If the 'present' flag is false then return first key instance
         * If the 'fixed' flag is true then no GETNEXT advance for the key
         * Create a new return key val_value_t, then getcb_add_return_key
         */

        boolean lastentry = false;
        boolean gotnext = getnext_A(getnext,
                                    &lastentry,
                                    &k_t_a,
                                    a_fixed,
                                    a_present,
                                    &k_t_b,
                                    b_fixed,
                                    b_present,
                                    &k_t_c,
                                    c_fixed,
                                    c_present);
        if (!gotnext) {
            return (entries_done > 0) ? NO_ERR : ERR_NCX_NO_INSTANCE;
        }

        /* optional: check if any content-match nodes are present */
        boolean match_test_done = FALSE;
        val_value_t *match_val = GETCB_GET2_FIRST_MATCH(get2cb);
        for (; match_val; match_val =
                 GETCB_GET2_NEXT_MATCH(get2cb, match_val)) {

            /**** CHECK CONTENT NODES AGAINST THIS ENTRY ***/
            if (LOGDEBUG) {
                log_debug("\nGot content-match node '%s'\n",
                          VAL_NAME(match_val));
                val_dump_value(match_val, 2, LOG_DEBUG_DEBUG);
            }

            /* if the match test fails then need to setup next getnext
             * and skip the entry just returned from getnext_A
             * in that case, invoke the following code
             *
             *    getnext=true;
             *    a_present = true;
             *    b_present = true;
             *    c_present = true;
             *    continue;
             */
        }

        /* the flag applies to all entries so need to check
         * for all entries or none of them
         */
        GETCB_GET2_MATCH_TEST_DONE(get2cb) = match_test_done;

        /* add the return keys [a, b, c] */
        val_value_t *chval = agt_make_uint_leaf(obj, y_t_N_a, k_t_a, &res);
        if (chval == NULL) {
            return res;
        }
        if (a_fixed) {
            VAL_SET_FIXED_VALUE(chval);
        }
        getcb_add_return_key(get2cb, chval);

        chval = agt_make_uint_leaf(obj, y_t_N_b, k_t_b, &res);
        if (chval == NULL) {
            return res;
        }
        if (b_fixed) {
            VAL_SET_FIXED_VALUE(chval);
        }
        getcb_add_return_key(get2cb, chval);

        chval = agt_make_uint_leaf(obj, y_t_N_c, k_t_c, &res);
        if (chval == NULL) {
            return res;
        }
        if (c_fixed) {
            VAL_SET_FIXED_VALUE(chval);
        }
        getcb_add_return_key(get2cb, chval);

        /* For GETNEXT, set the more_data flag to TRUE unless last entry */
        GETCB_GET2_MORE_DATA(get2cb) = !lastentry;

        if (!GETCB_GET2_KEYS_ONLY(get2cb)) {
            /* go through all the requested terminal child objects */
            obj_template_t *childobj =
                getcb_first_requested_child(get2cb, obj);
            for (; childobj; childobj =
                     getcb_next_requested_child(get2cb, childobj)) {

                const xmlChar *name = obj_get_name(childobj);

                /* Retrieve the value of this terminal node and
                 * add with getcb_add_return_val */

                if (!xml_strcmp(name, y_t_N_d)) {
                    /* leaf d (string) */

                    xmlChar buff[64];
                    snprintf((char *)buff, 64, "[%u, %u, %u]",
                             k_t_a, k_t_b, k_t_c);

                    chval = agt_make_leaf(obj, y_t_N_d, buff, &res);
                    if (chval == NULL) {
                        return res;
                    }
                    getcb_add_return_val(get2cb, chval);
                }
            }
        }

        if (lastentry) {
            done = true;
        } else {
            /* save the entry as a a getbulk struct and check
             * if more entries allowed
             */
            getcb_finish_getbulk_entry(get2cb);
            if (++entries_done < max_entries) {
                /* setup next call to getnext_A */
                a_present = true;
                b_present = true;
                c_present = true;
                getnext = true;
            } else {
                /* hit max_entries so stop looking for more entries */
                done = true;
            }
        }
    }

    return res;

} /* u_t_A_get */



Files Used in this Example


  • t60-example.tar
    • t60.yang
    • Makefile
    • src
      • Makefile
      • y_t60.h
      • y_t60.c
      • u_t60.h
      • u_t60.c
  • cd t60
  • make
  • sudo make install
  • run server with --module=t60 parameter to load SIL code