initial import of dispatcher
this code implements a way of registering path handlers and then calling those handlers based on a path.
This commit is contained in:
parent
aed7dbfd8b
commit
3344adb487
4 changed files with 468 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -73,3 +73,4 @@ test/vagrant/site.mk
|
||||||
test/cicd/site.mk
|
test/cicd/site.mk
|
||||||
doc/html
|
doc/html
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
|
||||||
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \
|
clixon_xpath.c clixon_xpath_ctx.c clixon_xpath_eval.c clixon_xpath_function.c \
|
||||||
clixon_xpath_optimize.c clixon_xpath_yang.c \
|
clixon_xpath_optimize.c clixon_xpath_yang.c \
|
||||||
clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \
|
clixon_datastore.c clixon_datastore_write.c clixon_datastore_read.c \
|
||||||
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c
|
clixon_netconf_lib.c clixon_stream.c clixon_nacm.c clixon_client.c clixon_netns.c \
|
||||||
|
clixon_dispatcher.c
|
||||||
|
|
||||||
YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
|
YACCOBJS = lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
|
||||||
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \
|
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \
|
||||||
|
|
|
||||||
400
lib/src/clixon_dispatcher.c
Normal file
400
lib/src/clixon_dispatcher.c
Normal file
|
|
@ -0,0 +1,400 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Rubicon Communications LLC (Netgate)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* we start with a series of dispatcher_definitions, which are a
|
||||||
|
* path and handler.
|
||||||
|
*
|
||||||
|
* we break the path up into elements and build a tree out of them
|
||||||
|
* example:
|
||||||
|
*
|
||||||
|
* we start with two paths /a/b/c and /a/d with handler_c() and
|
||||||
|
* handler_d() as their handlers respectively.
|
||||||
|
*
|
||||||
|
* this produces a tree like this:
|
||||||
|
*
|
||||||
|
* [/] root_handler()
|
||||||
|
* [a] NULL
|
||||||
|
* [b] NULL
|
||||||
|
* [c] handler_c()
|
||||||
|
* [d] handler_d()
|
||||||
|
*
|
||||||
|
* NULL means that there is no handler defined - if the terminal
|
||||||
|
* element of the path has a NULL handler then you look for the
|
||||||
|
* closest ancestor that does.
|
||||||
|
*
|
||||||
|
* for example, if I lookup /a/b I get back a pointer to root_handler()
|
||||||
|
* if i lookup /a/d, I get handler_d().
|
||||||
|
*
|
||||||
|
* if a element has a key (/a/b=c) then the list element is
|
||||||
|
* marked with an = sign and without the key
|
||||||
|
* so /a/b=c creates multiple entries:
|
||||||
|
*
|
||||||
|
* [/]
|
||||||
|
* [a]
|
||||||
|
* [b=]
|
||||||
|
* [b]
|
||||||
|
*
|
||||||
|
* NOTE 1: there is not a mechanism to free the created structures since
|
||||||
|
* it is intended that this tree is created only at startup. if use case
|
||||||
|
* changes, this function is trivial.
|
||||||
|
*
|
||||||
|
* NOTE 2: there is no attempt to optimize list searching here, sorry. I
|
||||||
|
* do not think that the known use cases will get big enough to make the
|
||||||
|
* tree get too large. I do not recommend that you encode every possible
|
||||||
|
* path, just top level key handlers.
|
||||||
|
*
|
||||||
|
* there are 2 functions to the API:
|
||||||
|
* clixon_register_handler(): build the dispatcher table
|
||||||
|
* clixon_call_handlers(): query the dispatcher table
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Important for writing handlers: a handler must return a complete
|
||||||
|
* valid response. It must operate in isolation, it must not expect
|
||||||
|
* any ordering in the calls and [under review] it should not call
|
||||||
|
* another handler directly or indirectly. Responses must me bound
|
||||||
|
* to a yang model and properly sorted and indexed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "clixon_dispatcher.h"
|
||||||
|
|
||||||
|
/* ===== utility routines ==== */
|
||||||
|
|
||||||
|
#define PATH_CHUNKS 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spilt a path into elements
|
||||||
|
*
|
||||||
|
* given an api-path, break it up into chunks separated by '/'
|
||||||
|
* characters. it is expected that api-paths are URI encoded, so no need
|
||||||
|
* to deal with unescaped special characters like ', ", and /
|
||||||
|
*
|
||||||
|
* @param path [input] path string
|
||||||
|
* @param plist [output] pointer to split path array
|
||||||
|
* @param plist_len [output] pointer to storage space for path array length
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void split_path(char *path, char ***plist, size_t *plist_len)
|
||||||
|
{
|
||||||
|
size_t allocated = PATH_CHUNKS;
|
||||||
|
|
||||||
|
/* don't modify the original copy */
|
||||||
|
char *work = strdup(path);
|
||||||
|
|
||||||
|
char **list = malloc(allocated * sizeof(char *));
|
||||||
|
memset(list, 0, allocated * sizeof(char *));
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
char *ptr = work;
|
||||||
|
if (*ptr == '/') {
|
||||||
|
char *new_element = strdup("/");
|
||||||
|
list[len++] = new_element;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = strtok(ptr, "/");
|
||||||
|
|
||||||
|
while (ptr != NULL) {
|
||||||
|
if (len > allocated) {
|
||||||
|
/* we've run out of space, allocate a bigger list */
|
||||||
|
allocated += PATH_CHUNKS;
|
||||||
|
list = realloc(list, allocated * sizeof(char *));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *new_element = strdup(ptr);
|
||||||
|
list[len++] = new_element;
|
||||||
|
|
||||||
|
ptr = strtok(NULL, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
*plist = list;
|
||||||
|
*plist_len = len;
|
||||||
|
|
||||||
|
free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* free a split path structure
|
||||||
|
*
|
||||||
|
* @param list [input] pointer to split path array
|
||||||
|
* @param len [input] length of split path array
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void split_path_free(char **list, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
free(list[i]);
|
||||||
|
}
|
||||||
|
free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find a peer of this node by name
|
||||||
|
* search through the list pointed at by peer
|
||||||
|
*
|
||||||
|
* @param node [input] pointer to a node in the peer list
|
||||||
|
* @param node_name [input] name of node we're looking for
|
||||||
|
* @return pointer to found node or NULL
|
||||||
|
*/
|
||||||
|
|
||||||
|
static dispatcher_entry_t *find_peer(dispatcher_entry_t *node, char *node_name)
|
||||||
|
{
|
||||||
|
if ((node == NULL) || (node_name == NULL)) {
|
||||||
|
/* protect against idiot users */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcher_entry_t *i = node->peer_head;
|
||||||
|
|
||||||
|
while (i != NULL) {
|
||||||
|
if (strcmp(node_name, i->node_name) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i = i->peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a node as the last node in peer list
|
||||||
|
*
|
||||||
|
* @param node [input] pointer to an element of the peer list
|
||||||
|
* @param name [input] name of new node
|
||||||
|
* @return pointer to added/existing node
|
||||||
|
*/
|
||||||
|
|
||||||
|
static dispatcher_entry_t *add_peer_node(dispatcher_entry_t *node, char *name)
|
||||||
|
{
|
||||||
|
dispatcher_entry_t *new_node = malloc(sizeof(dispatcher_entry_t));
|
||||||
|
memset(new_node, 0, sizeof(dispatcher_entry_t));
|
||||||
|
|
||||||
|
if (node == NULL) {
|
||||||
|
/* this is a new node */
|
||||||
|
|
||||||
|
new_node->node_name = strdup(name);
|
||||||
|
new_node->peer = NULL;
|
||||||
|
new_node->children = NULL;
|
||||||
|
new_node->peer_head = new_node;
|
||||||
|
|
||||||
|
return new_node;
|
||||||
|
} else {
|
||||||
|
/* possibly adding to the list */
|
||||||
|
|
||||||
|
/* search for existing, or get tail end of list */
|
||||||
|
dispatcher_entry_t *eptr = node->peer_head;
|
||||||
|
while (eptr->peer != NULL) {
|
||||||
|
if (strcmp(eptr->node_name, name) == 0) {
|
||||||
|
return eptr;
|
||||||
|
}
|
||||||
|
eptr = eptr->peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if eptr->node_name == name, we done
|
||||||
|
if (strcmp(eptr->node_name, name) == 0) {
|
||||||
|
return eptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_node->node_name = strdup(name);
|
||||||
|
new_node->peer = NULL;
|
||||||
|
new_node->children = NULL;
|
||||||
|
new_node->peer_head = node->peer_head;
|
||||||
|
|
||||||
|
eptr->peer = new_node;
|
||||||
|
|
||||||
|
return new_node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a node as a child of this node
|
||||||
|
*
|
||||||
|
* this is different from add_peer_node() in that it returns a
|
||||||
|
* pointer to the head_peer of the children list where the node was
|
||||||
|
* added.
|
||||||
|
*
|
||||||
|
* @param node [input] pointer to parent node of children list
|
||||||
|
* @param name [input] name of child node
|
||||||
|
* @return pointer to head of children list
|
||||||
|
*/
|
||||||
|
|
||||||
|
static dispatcher_entry_t *add_child_node(dispatcher_entry_t *node, char *name)
|
||||||
|
{
|
||||||
|
dispatcher_entry_t *child_ptr = add_peer_node(node->children, name);
|
||||||
|
node->children = child_ptr->peer_head;
|
||||||
|
|
||||||
|
return child_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param root
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
static dispatcher_entry_t *get_entry(dispatcher_entry_t *root, char *path)
|
||||||
|
{
|
||||||
|
char **split_path_list = NULL;
|
||||||
|
size_t split_path_len = 0;
|
||||||
|
dispatcher_entry_t *ptr = root;
|
||||||
|
dispatcher_entry_t *best = root;
|
||||||
|
|
||||||
|
/* cut the path up into individual elements */
|
||||||
|
split_path(path, &split_path_list, &split_path_len);
|
||||||
|
|
||||||
|
/* some elements may have keys defined, strip them off */
|
||||||
|
for (int i = 0; i < split_path_len; i++) {
|
||||||
|
char *kptr = strchr(split_path_list[i], '=');
|
||||||
|
|
||||||
|
if ((kptr != NULL) && (*kptr == '=')) {
|
||||||
|
*(kptr + 1) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* search down the tree */
|
||||||
|
for (int i = 0; i < split_path_len; i++) {
|
||||||
|
|
||||||
|
char *query = split_path_list[i];
|
||||||
|
ptr = find_peer(ptr, query);
|
||||||
|
|
||||||
|
if (ptr == NULL) {
|
||||||
|
/* we ran out of matches, use last found handler */
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
if (ptr->handler != NULL) {
|
||||||
|
/* if handler is defined, save it */
|
||||||
|
best = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* skip to next element */
|
||||||
|
ptr = ptr->children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given a pointer to an entry, call the handler and all
|
||||||
|
* descendant and peer handlers.
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int call_handler_helper(dispatcher_entry_t *entry, char *path, void *user_args)
|
||||||
|
{
|
||||||
|
if (entry->children != NULL) {
|
||||||
|
call_handler_helper(entry->children, path, user_args);
|
||||||
|
}
|
||||||
|
if (entry->peer != NULL) {
|
||||||
|
call_handler_helper(entry->peer, path, user_args);
|
||||||
|
}
|
||||||
|
if (entry->handler != NULL) {
|
||||||
|
(entry->handler) (path, user_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===== PUBLIC API FUNCTIONS =====
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register a dispatcher handler
|
||||||
|
*
|
||||||
|
* called from initialization code to build a dispatcher tree
|
||||||
|
*
|
||||||
|
* @param root pointer to pointer to dispatch tree
|
||||||
|
* @param x handler to registration data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
dispatcher_entry_t *clixon_register_handler(dispatcher_entry_t **root, dispatcher_definition *x)
|
||||||
|
{
|
||||||
|
char **split_path_list = NULL;
|
||||||
|
size_t split_path_len = 0;
|
||||||
|
|
||||||
|
if (*x->path != '/') {
|
||||||
|
printf("%s: part '%s' must start at root\n", __func__, x->path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get the path from the dispatcher_definition, break it
|
||||||
|
* up to create the elements of the dispatcher table
|
||||||
|
*/
|
||||||
|
split_path(x->path, &split_path_list, &split_path_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the first element is always a peer to the top level
|
||||||
|
*/
|
||||||
|
dispatcher_entry_t *ptr = *root;
|
||||||
|
|
||||||
|
ptr = add_peer_node(ptr, split_path_list[0]);
|
||||||
|
if (*root == NULL) {
|
||||||
|
*root = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 1; i < split_path_len; i++) {
|
||||||
|
ptr = add_child_node(ptr, split_path_list[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when we get here, ptr points at last entry added */
|
||||||
|
if (x->handler != NULL) {
|
||||||
|
/*
|
||||||
|
* we're adding/changing a handler
|
||||||
|
* you could make this an error optionally
|
||||||
|
*/
|
||||||
|
if (ptr->handler != NULL) {
|
||||||
|
printf("%s: warning: replacing existing handler: (%s) %p -> %p\n", __func__,
|
||||||
|
ptr->node_name, ptr->handler, x->handler);
|
||||||
|
}
|
||||||
|
ptr->handler = x->handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clean up */
|
||||||
|
split_path_free(split_path_list, split_path_len);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call the handler and all its descendant handlers
|
||||||
|
*
|
||||||
|
* NOTE: There is no guarantee of the order in which handlers
|
||||||
|
* are called! Any handler must assume that it is called in
|
||||||
|
* isolation, even if this duplicates work. The right to
|
||||||
|
* reorder calls by this code is reserved.
|
||||||
|
*
|
||||||
|
* @param root
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
int clixon_call_handlers(dispatcher_entry_t *root, char *path, void *user_args)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
dispatcher_entry_t *best = get_entry(root, path);
|
||||||
|
|
||||||
|
if (best->children != NULL) {
|
||||||
|
call_handler_helper(best->children, path, user_args);
|
||||||
|
}
|
||||||
|
if (best->handler != NULL) {
|
||||||
|
ret = (*best->handler) (path, user_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
65
lib/src/clixon_dispatcher.h
Normal file
65
lib/src/clixon_dispatcher.h
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Rubicon Communications LLC (Netgate)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISPATCH_DISPATCHER_H
|
||||||
|
#define DISPATCH_DISPATCHER_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* prototype for a function to handle the path
|
||||||
|
* minimally needs the path it's working on, but probably
|
||||||
|
* we want to hand down cached data somehow
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef int (*handler_function)(char *path, void *args);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* this structure is used to map a handler to a path
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char *path;
|
||||||
|
handler_function handler;
|
||||||
|
} dispatcher_definition;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the dispatcher_entry_t is the structure created from
|
||||||
|
* the registered dispatcher_definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct _dispatcher_entry;
|
||||||
|
typedef struct _dispatcher_entry dispatcher_entry_t;
|
||||||
|
|
||||||
|
struct _dispatcher_entry {
|
||||||
|
/*
|
||||||
|
* the name of this node, NOT the complete path
|
||||||
|
*/
|
||||||
|
char *node_name;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* peer points at peer to the right of this one
|
||||||
|
* if NULL then this is the rightmost and last on list
|
||||||
|
*/
|
||||||
|
dispatcher_entry_t *peer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* peer_head points at leftmost peer at this level
|
||||||
|
* if NULL, then this is the leftmost and first on the list
|
||||||
|
*/
|
||||||
|
dispatcher_entry_t *peer_head;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* points at peer_head of children list
|
||||||
|
* if NULL, then no children
|
||||||
|
*/
|
||||||
|
dispatcher_entry_t *children;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pointer to handler function for this node
|
||||||
|
*/
|
||||||
|
handler_function handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatcher_entry_t *clixon_register_handler (dispatcher_entry_t **root, dispatcher_definition *x);
|
||||||
|
int clixon_call_handlers(dispatcher_entry_t *root, char *path, void *user_args);
|
||||||
|
|
||||||
|
#endif /* DISPATCH_DISPATCHER_H */
|
||||||
Loading…
Add table
Add a link
Reference in a new issue