/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2021 Rubicon Communications, LLC(Netgate) This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * @see https://github.com/dcornejo/dispatcher */ /* * 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 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 #include #include #include #include #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[in] path Path string * @param[in] plist Pointer to split path array * @param[out] plist_len Pointer to storage space for path array length * @retval 0 OK * @retval -1 Error * XXX consider using clixon_strsep */ static int split_path(char *path, char ***plist, size_t *plist_len) { int retval = -1; size_t allocated = PATH_CHUNKS; char *work; /* don't modify the original copy */ char **list; size_t len = 0; char *ptr; char *new_element; if ((work = strdup(path)) == NULL) goto done; if ((list = malloc(allocated * sizeof(char *))) == NULL) goto done; memset(list, 0, allocated * sizeof(char *)); ptr = work; if (*ptr == '/') { if ((new_element = strdup("/")) == NULL) goto done; 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; if ((list = realloc(list, allocated * sizeof(char *))) == NULL) goto done; } if ((new_element = strdup(ptr)) == NULL) goto done; list[len++] = new_element; ptr = strtok(NULL, "/"); } *plist = list; *plist_len = len; free(work); retval = 0; done: return retval; } /*! Free a split path structure * * @param[in] list pointer to split path array * @param[in] len 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[in] node Pointer to a node in the peer list * @param[in] node_name Name of node we're looking for * @retval pointer Pointer to found node or NULL * @retval NULL */ static dispatcher_entry_t * find_peer(dispatcher_entry_t *node, char *node_name) { dispatcher_entry_t *i; if ((node == NULL) || (node_name == NULL)) { /* protect against idiot users */ return NULL; } 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[in] node Pointer to an element of the peer list * @param[in] name Name of new node * @retval pointer Pointer to added/existing node * @retval NULL Error */ static dispatcher_entry_t * add_peer_node(dispatcher_entry_t *node, char *name) { dispatcher_entry_t *new_node = NULL; dispatcher_entry_t *eptr; if ((new_node = malloc(sizeof(dispatcher_entry_t))) == NULL) return NULL; 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 */ 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[in] node Pointer to parent node of children list * @param[in] name Name of child node * @retval pointer Pointer to head of children list * @retval NULL Error */ static dispatcher_entry_t * add_child_node(dispatcher_entry_t *node, char *name) { dispatcher_entry_t *child_ptr; if ((child_ptr = add_peer_node(node->children, name)) == NULL) return NULL; node->children = child_ptr->peer_head; return child_ptr; } /** * * @param root * @param path * @retval * @retval NULL Error */ 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 */ if (split_path(path, &split_path_list, &split_path_len) < 0) return NULL; /* some elements may have keys defined, strip them off */ for (int i = 0; i < split_path_len; i++) { char *kptr = split_path_list[i]; strsep(&kptr, "=[]"); } /* search down the tree */ for (int i = 0; i < split_path_len; i++) { char *query = split_path_list[i]; if ((ptr = find_peer(ptr, query)) == NULL) { split_path_free(split_path_list, split_path_len); /* 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; } /* clean up */ split_path_free(split_path_list, split_path_len); return best; } /** * given a pointer to an entry, call the handler and all * descendant and peer handlers. * * @param entry * @param path * @retval */ static int call_handler_helper(dispatcher_entry_t *entry, void *handle, char *path, void *user_args) { if (entry->children != NULL) { call_handler_helper(entry->children, handle, path, user_args); } if (entry->peer != NULL) { call_handler_helper(entry->peer, handle, path, user_args); } if (entry->handler != NULL) { (entry->handler)(handle, path, user_args, entry->arg); } return 1; } /* * ===== PUBLIC API FUNCTIONS ===== */ /*! Register a dispatcher handler * * called from initialization code to build a dispatcher tree * * @param[in] root Pointer to pointer to dispatch tree * @param[in] x Handler to registration data * @retval 0 OK * @retval -1 Error */ int dispatcher_register_handler(dispatcher_entry_t **root, dispatcher_definition *x) { char **split_path_list = NULL; size_t split_path_len = 0; dispatcher_entry_t *ptr; if (*x->dd_path != '/') { errno = EINVAL; return -1; } /* * get the path from the dispatcher_definition, break it * up to create the elements of the dispatcher table */ if (split_path(x->dd_path, &split_path_list, &split_path_len) < 0) return -1; /* * the first element is always a peer to the top level */ ptr = *root; if ((ptr = add_peer_node(ptr, split_path_list[0])) == NULL) return -1; if (*root == NULL) { *root = ptr; } for (size_t i = 1; i < split_path_len; i++) { if ((ptr = add_child_node(ptr, split_path_list[i])) == NULL) return -1; } /* when we get here, ptr points at last entry added */ ptr->handler = x->dd_handler; ptr->arg = x->dd_arg; /* clean up */ split_path_free(split_path_list, split_path_len); return 0; } /*! 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[in] handle * @param[in] root * @param[in] path Note must be on the form: /a/b (no keys) * @retval 1 OK * @retval 0 Invalid * @retval -1 Error */ int dispatcher_call_handlers(dispatcher_entry_t *root, void *handle, char *path, void *user_args) { int ret = 0; dispatcher_entry_t *best; if ((best = get_entry(root, path)) == NULL){ errno = ENOENT; return -1; } if (best->children != NULL) { call_handler_helper(best->children, handle, path, user_args); } if (best->handler != NULL) { ret = (*best->handler)(handle, path, user_args, best->arg); } return ret; } /*! Free a dispatcher tree */ int dispatcher_free(dispatcher_entry_t *root) { if (root == NULL) return 0; if (root->children) dispatcher_free(root->children); if (root->peer) dispatcher_free(root->peer); if (root->node_name) free(root->node_name); free(root); return 0; } /*! Pretty-print dispatcher tree */ #define INDENT 3 int dispatcher_print(FILE *f, int level, dispatcher_entry_t *de) { fprintf(f, "%*s%s", level*INDENT, "", de->node_name); if (de->handler) fprintf(f, " %p", de->handler); if (de->arg) fprintf(f, " (%p)", de->arg); fprintf(f, "\n"); if (de->children) dispatcher_print(f, level+1, de->children); if (de->peer) dispatcher_print(f, level, de->peer); return 0; }