How to add the support of the find_next_object() callback to the 5-c_stats example?

For completeness, I’ll add my test to the “reference example” pile.

Implemented a simple mapping from the IETF interfaces YANG model to a ifstatus.yang model that implements a subset of the interfaces state.

ifstatus.yang:

module ifstatus {
  namespace "http://tail-f.com/ns/example/if";
  prefix ifs;

  import tailf-common {
    prefix tailf;
  }

  revision 2019-07-22 {
    description "Initial revision.";
  }

  container interfaces-state {
    list interface {
      key name;
      config false;
      tailf:callpoint ifs;
      leaf name {
        type string;
      }
      container statistics {
	leaf in-octets {
	  type uint64;
	  mandatory true;
	}
	leaf in-unicast-pkts {
	  type uint64;
	  mandatory true;
	}
	leaf in-errors {
	  type uint32;
	  mandatory true;
	}
	leaf in-discards {
	  type uint32;
	  mandatory true;
	}
	leaf out-octets {
	  type uint64;
	  mandatory true;
	}
	leaf out-unicast-pkts {
	  type uint64;
	  mandatory true;
	}
	leaf out-errors {
	  type uint32;
	  mandatory true;
	}
	leaf out-discards {
	  type uint32;
	  mandatory true;
	}
      }
    }
  }
}

The ifstatus YANG model has a data provider application that get the data it provides from the IETF Interfaces YANG model data which is stored in the CDB operational datastore.

The data provider registers the get_elem(), get_next(), get_next_object(), and find_next_object() and get the data from the CDB operational datastore using for example cdb_get_values(). For this example you would only need the get_elem() (mandatory) and find_next_object() callbacks registered. Comment out the rest if you dare.

The data provider application:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>

#include <confd_lib.h>
#include <confd_dp.h>
#include <confd_cdb.h>
#include "ietf-interfaces.h"
#include "ifstatus.h"

#define USE_CASE_ASSUME_NOTHING 0
#define USE_CASE_OPTIMAL_FOR_LARGE_LISTS 1
#define USE_CASE_ALL_IN_DEMO 2

/* Our daemon context as a global variable */
static struct confd_daemon_ctx *dctx;
static int ctlsock;
static int workersock;
static int cdbsock;

static int s_init(struct confd_trans_ctx *tctx)
{
  confd_trans_set_fd(tctx, workersock);
  return CONFD_OK;
}

static int get_next(struct confd_trans_ctx *tctx,
                    confd_hkeypath_t *keypath,
                    long next)
{
  confd_value_t v;
  confd_tag_value_t tv[3];
  
  if (next == -1) {  /* first call */
    next = 0;
  }

  if (cdb_num_instances(cdbsock, "/if:interfaces-state/interface") <= next) {
    /* we have reached the end of the list */
    confd_data_reply_next_key(tctx, NULL, -1, -1);
    return CONFD_OK;
  }
  
  /* get the key */
  CONFD_SET_TAG_CDBBEGIN(&tv[0], if_interface, if__ns, next);
  CONFD_SET_TAG_NOEXISTS(&tv[1], if_name);
  CONFD_SET_TAG_XMLEND(&tv[2], if_interface, if__ns);

  if (cdb_get_values(cdbsock, tv, 3, "/if:interfaces-state") != CONFD_OK) {
    /* key not found */
    confd_data_reply_next_key(tctx, NULL, -1, -1);
    return CONFD_OK;
  }

  /* return the key from the list entry and 'next + 1' for next entry */
  CONFD_SET_STR(&v, CONFD_GET_CBUFPTR(CONFD_GET_TAG_VALUE(&tv[1])));
  confd_data_reply_next_key(tctx, &v, 1, next + 1);
  confd_free_value(&v); /* name must be freed since it's a C_BUF */
  return CONFD_OK;
}


static int get_elem(struct confd_trans_ctx *tctx,
                    confd_hkeypath_t *keypath)
{
  confd_value_t v;
  
  if (cdb_get(cdbsock, &v, "%h", keypath) != CONFD_OK) {
    confd_data_reply_not_found(tctx);
  } else {
    confd_data_reply_value(tctx, &v);
  }

  confd_free_value(&v); /* name must be freed since it's a C_BUF, other values is a nop */
  return CONFD_OK;
}

static int num_instances(struct confd_trans_ctx *tctx,
                         confd_hkeypath_t *keypath)
{
  confd_value_t v;
  
  CONFD_SET_INT32(&v, cdb_num_instances(cdbsock, "/if:interfaces-state/interface"));
  confd_data_reply_value(tctx, &v);
  return CONFD_OK;
}

static int ifs_max_obj_tvsize = 11;

static int get_object(struct confd_trans_ctx *tctx,
			   confd_hkeypath_t *keypath)
{
  confd_tag_value_t itv[ifs_max_obj_tvsize+2], tv[ifs_max_obj_tvsize];
  int pos, j;

  pos = cdb_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", &keypath->v[0][0]);

  if (pos < 0) {
    /* No list entry with a maching key */
    confd_data_reply_not_found(tctx);
    return CONFD_OK;
  }

  j = 0;
  CONFD_SET_TAG_CDBBEGIN(&itv[j], if_interface, if__ns, pos); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_name); j++;
  CONFD_SET_TAG_XMLBEGIN(&itv[j], if_statistics, if__ns); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_octets); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_unicast_pkts); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_errors); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_discards); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_octets); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_unicast_pkts); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_errors); j++;
  CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_discards); j++;
  CONFD_SET_TAG_XMLEND(&itv[j], if_statistics, if__ns); j++;
  CONFD_SET_TAG_XMLEND(&itv[j], if_interface, if__ns); j++;
  
  if (cdb_get_values(cdbsock, itv, j, "/if:interfaces-state") != CONFD_OK)
    confd_fatal("cdb_get_values() from /if:interfaces-state failed\n");

  j = 0;
  j++; /* get rid of the begin list tag */
  CONFD_SET_TAG_VALUE(&tv[0], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[1], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[2], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[3], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[4], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[5], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[6], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[7], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[8], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[9], itv[j].tag.tag, &itv[j].v); j++;
  CONFD_SET_TAG_VALUE(&tv[10], itv[j].tag.tag, &itv[j].v); j++;
  j++; /* dispose of the end list tag (here only for clarity) */
  
  confd_data_reply_tag_value_array(tctx, &tv[0], ifs_max_obj_tvsize);
  confd_free_value(&tv[0].v); /* name must be freed since it's a C_BUF */
  return CONFD_OK;
}
  
static int max_nobjs = 100;

static int get_next_object(struct confd_trans_ctx *tctx,
			   confd_hkeypath_t *keypath, long next)
{
  int pos, n_list_entries, nobj, i, j;
  confd_tag_value_t *tv, *itv;
  struct confd_tag_next_object *tobj;

  if (next == -1) {  /* first call */
    pos = 0;
  } else {
    pos = next;
  }

  if ((n_list_entries = cdb_num_instances(cdbsock, "/if:interfaces-state/interface")) <= pos) {
    /* we have reached the end of the list */
    confd_data_reply_next_key(tctx, NULL, -1, -1);
    return CONFD_OK;
  }
  
  if (n_list_entries - pos > max_nobjs) {
    nobj = max_nobjs;
  } else {
    nobj = n_list_entries - pos;
  }
  
  //fprintf(stderr, "\nifs_max_obj_tvsize %d n_list_entries %d nobj %d pos %d\n", ifs_max_obj_tvsize, n_list_entries, nobj, pos);
  
  /* get the list entries */
  itv = (confd_tag_value_t *) malloc(sizeof(confd_tag_value_t) * nobj * (ifs_max_obj_tvsize + 2)); /* "+ 2" for the begin and end tags */
  j = 0;
  for (i = 0; i < nobj; i++) {
    CONFD_SET_TAG_CDBBEGIN(&itv[j], if_interface, if__ns, pos+i); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_name); j++;
    CONFD_SET_TAG_XMLBEGIN(&itv[j], if_statistics, if__ns); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_octets); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_unicast_pkts); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_errors); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_discards); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_octets); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_unicast_pkts); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_errors); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_discards); j++;
    CONFD_SET_TAG_XMLEND(&itv[j], if_statistics, if__ns); j++;
    CONFD_SET_TAG_XMLEND(&itv[j], if_interface, if__ns); j++;
  }

  if (cdb_get_values(cdbsock, itv, nobj * (ifs_max_obj_tvsize + 2), "/if:interfaces-state") != CONFD_OK)
    confd_fatal("cdb_get_values() from /if:interfaces-state failed\n");

  tobj = malloc(sizeof(struct confd_tag_next_object) * (max_nobjs + 1));
  tv = (confd_tag_value_t *) malloc(sizeof(confd_tag_value_t) * max_nobjs * ifs_max_obj_tvsize);
  
  /* create reply */
  j = 0;
  for (i = 0; i < nobj; i++) {
    tobj[i].tv = &tv[i * ifs_max_obj_tvsize];
    j++; /* get rid of the begin list tag */
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[0]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[1]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[2]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[3]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[4]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[5]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[6]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[7]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[8]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[9]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[10]), itv[j].tag.tag, &itv[j].v); j++;
    j++; /* dispose of the end list tag */
    tobj[i].n = ifs_max_obj_tvsize;
    tobj[i].next = (long)pos+i+1;
  }

  if (pos + i == n_list_entries)
    tobj[i++].tv = NULL; /* indicate no more list entries */

  /* reply */
  confd_data_reply_next_object_tag_value_arrays(tctx, tobj, i, 0);
  for (i = 0; pos + i < nobj; i++) {
    confd_free_value(CONFD_GET_TAG_VALUE(&(tobj[i].tv[0]))); /* name must be freed since it's a C_BUF */
  }
  free(itv);
  free(tv);
  free(tobj);
  return CONFD_OK;
}

static int find_next(struct confd_trans_ctx *tctx,
                     confd_hkeypath_t *kp,
                     enum confd_find_next_type type,
                     confd_value_t *keys, int nkeys)
{
  confd_value_t v;
  confd_tag_value_t tv[3];
  int pos = -1;
  
  switch (nkeys) {
  case 0:
    pos = 0; /* first call */
    break;
  case 1:
    switch (type) {
    case CONFD_FIND_SAME_OR_NEXT:
      if((pos = cdb_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys)) < 0) {
	pos = cdb_next_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys);
      }
      break;
    case CONFD_FIND_NEXT:
      pos = cdb_next_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys);
      break;
    default:
      confd_fatal("invalid find next type");
      break;
    }
    
    if (pos < 0) {
      /* key does not exist */
      confd_data_reply_next_key(tctx, NULL, -1, -1);
      return CONFD_OK;
    }  
    break;
  default:
    confd_fatal("invalid number of keys");
    break;
  }

  /* get the key */
  CONFD_SET_TAG_CDBBEGIN(&tv[0], if_interface, if__ns, pos);
  CONFD_SET_TAG_NOEXISTS(&tv[1], if_name);
  CONFD_SET_TAG_XMLEND(&tv[2], if_interface, if__ns);

  if (cdb_get_values(cdbsock, tv, 3, "/if:interfaces-state") != CONFD_OK) {
    /* key not found in the unlikely event that it was deleted after our 
       cdb_index() check */
    confd_data_reply_next_key(tctx, NULL, -1, -1);
    return CONFD_OK;
  }
  
  CONFD_SET_STR(&v, CONFD_GET_CBUFPTR(CONFD_GET_TAG_VALUE(&tv[1])));

  /* reply */
  confd_data_reply_next_key(tctx, &v, 1, pos+1);
  confd_free_value(&v);
  
  return CONFD_OK;
}

static int find_next_object(struct confd_trans_ctx *tctx,
			    confd_hkeypath_t *keypath,
			    enum confd_find_next_type type,
                            confd_value_t *keys, int nkeys)
{
  int pos = 0, n_list_entries, nobj, i, j;
  confd_tag_value_t *tv, *itv;
  struct confd_tag_next_object *tobj;

  switch (nkeys) {
  case 0:
    pos = 0; /* first call */
    break;
  case 1:
    switch (type) {
    case CONFD_FIND_SAME_OR_NEXT:
      if((pos = cdb_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys)) < 0) {
	pos = cdb_next_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys);
      }
      break;
    case CONFD_FIND_NEXT:
      pos = cdb_next_index(cdbsock, "%s{%x}", "/if:interfaces-state/interface", keys);
      break;
    default:
      confd_fatal("invalid find next type");
      break;
    }
    break;
  default:
    confd_fatal("invalid number of keys");
    break;
  }

  if (pos == -1 || (n_list_entries = cdb_num_instances(cdbsock, "/if:interfaces-state/interface")) <= pos) {
    /* we have reached the end of the list */
    confd_data_reply_next_key(tctx, NULL, -1, -1);
    return CONFD_OK;
  }
  
  if (n_list_entries - pos > max_nobjs) {
    nobj = max_nobjs;
  } else {
    nobj = n_list_entries - pos;
  }
  
  //fprintf(stderr, "\nifs_max_obj_tvsize %d n_list_entries %d nobj %d pos %d nkeys %d\n", ifs_max_obj_tvsize, n_list_entries, nobj, pos, nkeys);
  
  /* get the list entries */
  itv = (confd_tag_value_t *) malloc(sizeof(confd_tag_value_t) * nobj * (ifs_max_obj_tvsize + 2)); /* "+ 2" for the begin and end tags */
  j = 0;
  for (i = 0; i < nobj; i++) {
    CONFD_SET_TAG_CDBBEGIN(&itv[j], if_interface, if__ns, pos+i); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_name); j++;
    CONFD_SET_TAG_XMLBEGIN(&itv[j], if_statistics, if__ns); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_octets); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_unicast_pkts); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_errors); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_in_discards); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_octets); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_unicast_pkts); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_errors); j++;
    CONFD_SET_TAG_NOEXISTS(&itv[j], if_out_discards); j++;
    CONFD_SET_TAG_XMLEND(&itv[j], if_statistics, if__ns); j++;
    CONFD_SET_TAG_XMLEND(&itv[j], if_interface, if__ns); j++;
  }

  if (cdb_get_values(cdbsock, itv, nobj * (ifs_max_obj_tvsize + 2), "/if:interfaces-state") != CONFD_OK)
    confd_fatal("cdb_get_values() from /if:interfaces-state failed\n");

  tobj = malloc(sizeof(struct confd_tag_next_object) * (max_nobjs + 1));
  tv = (confd_tag_value_t *) malloc(sizeof(confd_tag_value_t) * max_nobjs * ifs_max_obj_tvsize);
  
  /* create reply */
  j = 0;
  for (i = 0; i < nobj; i++) {
    tobj[i].tv = &tv[i * ifs_max_obj_tvsize];
    j++; /* get rid of the begin list tag */
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[0]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[1]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[2]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[3]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[4]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[5]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[6]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[7]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[8]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[9]), itv[j].tag.tag, &itv[j].v); j++;
    CONFD_SET_TAG_VALUE(&(tobj[i].tv[10]), itv[j].tag.tag, &itv[j].v); j++;
    j++; /* dispose of the end list tag */
    tobj[i].n = ifs_max_obj_tvsize;
    tobj[i].next = (long)pos+i+1;
  }

  if (pos + i >= n_list_entries)
    tobj[i++].tv = NULL; /* indicate no more list entries */

  /* reply */
  confd_data_reply_next_object_tag_value_arrays(tctx, tobj, i, 0);
  for (i = 0; pos + i < nobj; i++) {
    confd_free_value(CONFD_GET_TAG_VALUE(&(tobj[i].tv[0]))); /* name must be freed since it's a C_BUF */
  }
  free(itv);
  free(tv);
  free(tobj);
  return CONFD_OK;
}

int main(int argc, char *argv[])
{
  struct sockaddr_in addr;
  int c, debuglevel = CONFD_DEBUG;
  int use_case_type = USE_CASE_ASSUME_NOTHING;
  struct confd_trans_cbs trans;
  struct confd_data_cbs data;

  while ((c = getopt(argc, argv, "noaxzdpts")) != EOF) {
    switch(c) {
    case 'n':
      use_case_type = USE_CASE_ASSUME_NOTHING;
      break;
    case 'o':
      use_case_type = USE_CASE_OPTIMAL_FOR_LARGE_LISTS;
      break;
    case 'a':
      use_case_type = USE_CASE_ALL_IN_DEMO;
      break;  
    case 'x':
      max_nobjs = atoi(optarg);
      break;
    case 'z':
      ifs_max_obj_tvsize = atoi(optarg);
      break;
    case 'd':
      debuglevel = CONFD_DEBUG;
      break;
    case 'p':
      debuglevel = CONFD_PROTO_TRACE;
      break;
    case 't':
      debuglevel = CONFD_TRACE;
      break;
    case 's':
      debuglevel = CONFD_SILENT;
      break;
    }
  }
  
  /* Initialize confd library */
  confd_init("ifstatus-cdb", stderr, debuglevel);

  addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  addr.sin_family = AF_INET;
  addr.sin_port = htons(CONFD_PORT);

  if (confd_load_schemas((struct sockaddr*)&addr,
			 sizeof (struct sockaddr_in)) != CONFD_OK)
    confd_fatal("Failed to load schemas from confd\n");
  
  if ((dctx = confd_init_daemon("ifstatus-cdb")) == NULL)
    confd_fatal("Failed to initialize daemon\n");

  /* Create and connect the control and worker sockets */
  if ((ctlsock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
    confd_fatal("Failed to open ctlsocket\n");
  if (confd_connect(dctx, ctlsock, CONTROL_SOCKET, (struct sockaddr*)&addr,
		    sizeof (struct sockaddr_in)) < 0)
    confd_fatal("Failed to confd_connect() to confd \n");

  if ((workersock = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
    confd_fatal("Failed to open workersocket\n");
  if (confd_connect(dctx, workersock, WORKER_SOCKET,(struct sockaddr*)&addr,
		    sizeof (struct sockaddr_in)) < 0)
    confd_fatal("Failed to confd_connect() to confd \n");

  /* Register callbacks */
  memset(&trans, 0, sizeof(trans));
  trans.init = s_init;
  if (confd_register_trans_cb(dctx, &trans) == CONFD_ERR)
    confd_fatal("Failed to register trans cb\n");

  memset(&data, 0, sizeof (struct confd_data_cbs));
  switch (use_case_type) {
  case USE_CASE_OPTIMAL_FOR_LARGE_LISTS:
    /* assuming large lists and not the content of 
       individual leafs are typically requested */
    data.num_instances = num_instances;
    data.get_object = get_object;
    data.find_next = find_next;
    data.find_next_object = find_next_object;
    break;
  case USE_CASE_ALL_IN_DEMO:
    /* going "all in" as a demo */
    data.get_elem = get_elem;
    data.get_object = get_object;
    data.num_instances = num_instances;
    data.get_next = get_next;
    data.get_next_object = get_next_object;
    data.find_next = find_next;
    data.find_next_object = find_next_object;
    break;
  default: /* USE_CASE_ASSUME_NOTHING */
    /* assuming nothing about the use case */
    data.get_elem = get_elem;
    data.num_instances = num_instances;
    data.get_object = get_object;
    data.find_next = find_next;
    data.find_next_object = find_next_object;
    break;
  }
  strcpy(data.callpoint, ifs__callpointid_ifs);
  if (confd_register_data_cb(dctx, &data) == CONFD_ERR)
    confd_fatal("Failed to register data cb\n");

  if (confd_register_done(dctx) != CONFD_OK)
    confd_fatal("Failed to complete registration \n");

  /* Start a CDB session towards the CDB operational datastore */
  if ((cdbsock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    confd_fatal("Failed to create the CDB socket");
  if (cdb_connect(cdbsock, CDB_DATA_SOCKET, (struct sockaddr *)&addr,
		  sizeof(struct sockaddr_in)) < 0)
    confd_fatal("Failed to connect to ConfD CDB");
  if (cdb_start_session(cdbsock, CDB_OPERATIONAL) != CONFD_OK)
    confd_fatal("Failed to start a CDB session\n");
  if (cdb_set_namespace(cdbsock, if__ns) != CONFD_OK)
    confd_fatal("Failed to set namespace\n");
  
  while(1) {
    struct pollfd set[2];
    int ret;

    set[0].fd = ctlsock;
    set[0].events = POLLIN;
    set[0].revents = 0;

    set[1].fd = workersock;
    set[1].events = POLLIN;
    set[1].revents = 0;

    if (poll(set, sizeof(set)/sizeof(set[0]), -1) < 0) {
      perror("Poll failed:");
      continue;
    }

    /* Check for I/O */
    if (set[0].revents & POLLIN) {
      if ((ret = confd_fd_ready(dctx, ctlsock)) == CONFD_EOF) {
	confd_fatal("Control socket closed\n");
      } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
	confd_fatal("Error on control socket request: %s (%d): %s\n",
		    confd_strerror(confd_errno), confd_errno, confd_lasterr());
      }
    }
    if (set[1].revents & POLLIN) {
      if ((ret = confd_fd_ready(dctx, workersock)) == CONFD_EOF) {
	confd_fatal("Worker socket closed\n");
      } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
	confd_fatal("Error on worker socket request: %s (%d): %s\n",
		    confd_strerror(confd_errno), confd_errno, confd_lasterr());
      }
    }
  }
}

The MAAPI client that get some list entries from the ifstatus.yang list using maapi_get_next() and maapi_get_objects() that trigger the find_next_object() callback:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <confd_lib.h>
#include <confd_maapi.h>
#include "ietf-interfaces.h"
#include "ifstatus.h"

static void print_value_array(confd_value_t vs[], int n)
{
    char tmpbuf[BUFSIZ];
    int i;

    for (i=0; i<n; i++) {
      confd_pp_value(tmpbuf, BUFSIZ, &vs[i]);
      printf("%s ", tmpbuf);
    }
}

int main(int argc, char *argv[])
{
  struct sockaddr_in addr;
  struct maapi_cursor mc;
  int c, i, thandle, maapisock, values_per_entry, nobj;
  int debuglevel = CONFD_DEBUG, entries_per_request = 5;
  confd_value_t *v, inkeys[1];
  struct confd_cs_node *object;
  struct confd_ip ip;
  const char *groups[] = { "admin" }, *context = "system";
  char *inkey = "lo1";
  
  while ((c = getopt(argc, argv, "k:e:dpts")) != EOF) {
    switch(c) {
    case 'k':
      inkey = optarg;
      break;
    case 'e':
      entries_per_request = atoi(optarg);
      break;
    case 'd':
      debuglevel = CONFD_DEBUG;
      break;
    case 'p':
      debuglevel = CONFD_PROTO_TRACE;
      break;
    case 't':
      debuglevel = CONFD_TRACE;
      break;
    case 's':
      debuglevel = CONFD_SILENT;
      break;
    }
  }
 
  confd_init("maapi-get-obj", stderr, debuglevel);

  addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  addr.sin_family = AF_INET;
  addr.sin_port = htons(CONFD_PORT);

  if (confd_load_schemas((struct sockaddr*)&addr,
			 sizeof (struct sockaddr_in)) != CONFD_OK)
    confd_fatal("Failed to load schemas from confd\n");

  object = confd_cs_node_cd(NULL, "/ifs:interfaces-state/interface");
  values_per_entry = confd_max_object_size(object);
  
  if ((maapisock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    confd_fatal("Failed to create the MAAPI socket");
  if (maapi_connect(maapisock, (struct sockaddr*)&addr,
		    sizeof (struct sockaddr_in)) < 0)
    confd_fatal("Failed to confd_connect() to confd \n");

  ip.af = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &ip.ip.v4);
  
  if ((maapi_start_user_session(maapisock, "admin", context,groups,
				sizeof(groups) / sizeof(*groups),
				&ip,
				CONFD_PROTO_TCP) != CONFD_OK)) {
    confd_fatal("Failed to start user session");
  }

  if ((thandle = maapi_start_trans(maapisock,CONFD_OPERATIONAL,
			      CONFD_READ)) < 0) {
    confd_fatal("Failed to start trans\n");
  }
  
  if(maapi_init_cursor(maapisock, thandle, &mc,
		       "/ifs:interfaces-state/interface") != CONFD_OK)
    confd_fatal("maapi_init_cursor() failed\n");

  CONFD_SET_STR(&inkeys[0], &inkey[0]);
  if (maapi_find_next(&mc, CONFD_FIND_SAME_OR_NEXT, inkeys, 1) != CONFD_OK)
    confd_fatal("maapi_find_next() failed\n");

  if(mc.n == 0) {
    fprintf(stderr, "Key \"%s\" not found\n", inkey);
    exit(0);
  }
  
  v = malloc(sizeof(confd_value_t) * values_per_entry * entries_per_request);
  nobj = entries_per_request;
  if (maapi_get_objects(&mc, v, values_per_entry, &nobj) < 0)
    confd_fatal("maapi_get_objects() failed\n");

  if(nobj == 0) {
    fprintf(stderr, "No entries after \"%s\"\n", inkey);
    exit(0);
  }
  
  for (i = 0; i < nobj; i++) {
    printf("%d: ", i);
    print_value_array(&v[i*values_per_entry], values_per_entry);
    printf("\n");
  }
  exit(0);
}

A simple script to generate some test data:

#!/usr/bin/env python

import sys
import time

def print_config(str, str2):
    print """<config xmlns="http://tail-f.com/ns/config/1.0">
    <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces" xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">
    <interface>
    <name>lo1</name>
    <type>ianaift:softwareLoopback</type>
    <enabled>true</enabled>
    </interface>
    %s
    </interfaces>
    <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces" xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">
    <interface>
    <name>lo1</name>
    <type>ianaift:softwareLoopback</type>
    <admin-status>up</admin-status>
    <oper-status>up</oper-status>
    <if-index>1</if-index>
    <statistics>
    <discontinuity-time>
    2019-07-22T03:00:00+00:00
    </discontinuity-time>
    <in-octets>0</in-octets>
    <in-unicast-pkts>0</in-unicast-pkts>
    <in-broadcast-pkts>0</in-broadcast-pkts>
    <in-multicast-pkts>0</in-multicast-pkts>
    <in-discards>0</in-discards>
    <in-errors>0</in-errors>
    <in-unknown-protos>0</in-unknown-protos>
    <out-octets>0</out-octets>
    <out-unicast-pkts>0</out-unicast-pkts>
    <out-broadcast-pkts>0</out-broadcast-pkts>
    <out-multicast-pkts>0</out-multicast-pkts>
    <out-discards>0</out-discards>
    <out-errors>0</out-errors>
    </statistics>
    </interface>
    %s
    </interfaces-state>
    </config>
    """%(str,str2)

def gen_data(n):
    str = ""
    str2 = ""
    for i in xrange(0,n):
        str += """
        <interface>
        <name>eth%03d</name>
        <type>ianaift:ethernetCsmacd</type>
        <enabled>true</enabled>
        </interface>
        """%(i)
        
        str2 += """
        <interface>
        <name>eth%03d</name>
        <type>ianaift:ethernetCsmacd</type>
        <admin-status>up</admin-status>
        <oper-status>up</oper-status>
        <if-index>%d</if-index>
        <phys-address>00:01:02:03:04:06</phys-address>
        <statistics>
        <discontinuity-time>
        2019-07-22T03:00:00+00:00
        </discontinuity-time>
        <in-octets>%d</in-octets>
        <in-unicast-pkts>%d</in-unicast-pkts>
        <in-broadcast-pkts>%d</in-broadcast-pkts>
        <in-multicast-pkts>%d</in-multicast-pkts>
        <in-discards>%d</in-discards>
        <in-errors>%d</in-errors>
        <in-unknown-protos>%d</in-unknown-protos>
        <out-octets>%d</out-octets>
        <out-unicast-pkts>%d</out-unicast-pkts>
        <out-broadcast-pkts>%d</out-broadcast-pkts>
        <out-multicast-pkts>%d</out-multicast-pkts>
        <out-discards>%d</out-discards>
        <out-errors>%d</out-errors>
        </statistics>
        </interface>
        """%(i,i+2,i,i,i,i,i,i,i,i,i,i,i,i,i)
    print_config(str, str2)

def parse_num(str):
    if str[:2] == '2^':
        return pow(2,parse_num(str[2:]))
    return int(str)

gen_data(parse_num(sys.argv[1]))

After building the YANG models and applications above you can for example generate some test data
$ ./cdbgen.py 1000 &gt; test.xml
Load it into the CDB operational datastore defined by the IETF interfaces YANG model
$ confd_load -O -dd -m -l test.xml
Run the maapi_get_next() maapi_get_object() client application to trigger the find_get_next()
$ ./maapi-get-obj -k eth100 -e 5

And/or run some netconf-console, confd_load, curl, etc. tests to exercise some some other interfaces and API calls.