| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| #include "parse_tree.h" |
| |
| #include "qpid/dispatch/alloc.h" |
| #include "qpid/dispatch/hash.h" |
| #include "qpid/dispatch/log.h" |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| |
| // token parsing |
| // parse a string of tokens separated by a boundary character. |
| // |
| // The format of a pattern string is a series of tokens. A token can contain |
| // any characters but those reserved for wildcards or the boundary character. |
| // |
| // TODO(kgiusti): really should create a token parsing qd_iterator_t view. |
| // Then all this token/token_iterator stuff can be removed... |
| // |
| typedef struct token { |
| const char *begin; |
| const char *end; |
| } token_t; |
| #define TOKEN_LEN(t) ((t).end - (t).begin) |
| |
| // for iterating over a string of tokens: |
| typedef struct token_iterator { |
| const char *separators; |
| const char *terminator; // end of entire string |
| token_t token; // token at head of string |
| char match_1; // match any 1 token |
| char match_glob; // match 0 or more tokens |
| } token_iterator_t; |
| |
| |
| static void token_iterator_init(token_iterator_t *t, |
| qd_parse_tree_type_t type, |
| const char *str) |
| { |
| switch (type) { |
| default: // See DISPATCH-1567: shuts up some bogus compiler warnings |
| assert(false); |
| // fall through |
| case QD_PARSE_TREE_ADDRESS: |
| t->separators = "./"; // accept either for backward compatibility |
| t->match_1 = '*'; |
| t->match_glob = '#'; |
| break; |
| case QD_PARSE_TREE_AMQP_0_10: |
| t->separators = "."; |
| t->match_1 = '*'; |
| t->match_glob = '#'; |
| break; |
| case QD_PARSE_TREE_MQTT: |
| t->separators = "/"; |
| t->match_1 = '+'; |
| t->match_glob = '#'; |
| break; |
| } |
| |
| while (*str && strchr(t->separators, *str)) |
| str++; // skip any leading separators |
| const char *tend = strpbrk(str, t->separators); |
| t->terminator = str + strlen(str); |
| t->token.begin = str; |
| t->token.end = (tend) ? tend : t->terminator; |
| } |
| |
| |
| static void token_iterator_next(token_iterator_t *t) |
| { |
| if (t->token.end == t->terminator) { |
| t->token.begin = t->terminator; |
| } else { |
| const char *tend; |
| t->token.begin = t->token.end + 1; |
| tend = strpbrk(t->token.begin, t->separators); |
| t->token.end = (tend) ? tend : t->terminator; |
| } |
| } |
| |
| |
| const char address_token_sep[] = "./"; |
| const char *qd_parse_address_token_sep() |
| { |
| return address_token_sep; |
| } |
| |
| static bool token_iterator_done(const token_iterator_t *t) |
| { |
| return t->token.begin == t->terminator; |
| } |
| |
| |
| static void token_iterator_pop(token_iterator_t *t, token_t *head) |
| { |
| if (head) *head = t->token; |
| token_iterator_next(t); |
| } |
| |
| |
| static bool token_match_str(const token_t *t, const char *str) |
| { |
| return (TOKEN_LEN(*t) == strlen(str) && !strncmp(t->begin, str, TOKEN_LEN(*t))); |
| } |
| |
| // True if current token is the match one wildcard |
| static bool token_iterator_is_match_1(const token_iterator_t *t) |
| { |
| return TOKEN_LEN(t->token) == 1 && |
| *t->token.begin == t->match_1; |
| } |
| |
| // True if current token is the match zero or more wildcard |
| static bool token_iterator_is_match_glob(const token_iterator_t *t) |
| { |
| return TOKEN_LEN(t->token) == 1 && |
| *t->token.begin == t->match_glob; |
| } |
| |
| |
| // Optimize the pattern match code by performing the following |
| // transformations to the pattern: |
| // #.* ---> *.# |
| // #.# ---> # |
| // This only applies to non-MQTT patterns since in MQTT it is illegal for the # |
| // to appear anywhere but the last token. |
| // Returns true if pattern was rewritten. |
| // |
| static bool normalize_pattern(qd_parse_tree_type_t type, char *pattern) |
| { |
| token_iterator_t t; |
| bool modified = false; |
| char *original = NULL; |
| |
| if (type == QD_PARSE_TREE_MQTT) return false; |
| |
| token_iterator_init(&t, type, pattern); |
| while (!token_iterator_done(&t)) { |
| if (token_iterator_is_match_glob(&t)) { |
| token_t last_token; |
| token_iterator_pop(&t, &last_token); |
| if (token_iterator_done(&t)) break; |
| if (token_iterator_is_match_glob(&t)) { // #.# --> # |
| if (!modified) original = strdup(pattern); |
| modified = true; |
| char *src = (char *)t.token.begin; |
| char *dest = (char *)last_token.begin; |
| // note: overlapping strings, can't strcpy |
| while (*src) |
| *dest++ = *src++; |
| *dest = (char)0; |
| t.terminator = dest; |
| t.token = last_token; |
| } else if (token_iterator_is_match_1(&t)) { // #.* --> *.# |
| if (!modified) original = strdup(pattern); |
| modified = true; |
| *(char *)t.token.begin = t.match_glob; |
| *(char *)last_token.begin = t.match_1; |
| } else { |
| token_iterator_next(&t); |
| } |
| } else { |
| token_iterator_next(&t); |
| } |
| } |
| |
| if (original) { |
| qd_log(qd_log_source("DEFAULT"), QD_LOG_NOTICE, |
| "configured pattern '%s' optimized and re-written to '%s'", |
| original, pattern); |
| free(original); |
| } |
| |
| return modified; |
| } |
| |
| |
| // Parse Tree |
| // |
| // The pattern is broken up into tokens delimited by a separation character and |
| // stored in a directed tree graph. Common pattern prefixes are merged. This |
| // allows the lookup alogrithm to quickly isolate those sub-trees that match a |
| // given input string. |
| // For example, given the patterns: |
| // a.b.c.<...> |
| // a.b.d.<...> |
| // a.x.y.<...> |
| // The resulting parse tree would be: |
| // a-->b-->c-->... |
| // | +-->d-->... |
| // +-->x-->y-->... |
| // |
| |
| |
| typedef struct qd_parse_node qd_parse_node_t; |
| |
| struct qd_parse_tree { |
| qd_parse_node_t *root; |
| qd_log_source_t *log_source; |
| qd_hash_t *hash; |
| qd_parse_tree_type_t type; |
| uint32_t next_hkey_prefix; // next # for hash key prefix |
| }; |
| ALLOC_DECLARE(qd_parse_tree_t); |
| ALLOC_DEFINE(qd_parse_tree_t); |
| |
| |
| typedef enum { |
| QD_PARSE_NODE_ROOT, |
| QD_PARSE_NODE_TOKEN, |
| QD_PARSE_NODE_MATCH_ONE, |
| QD_PARSE_NODE_MATCH_GLOB, |
| } qd_parse_node_type_t; |
| |
| DEQ_DECLARE(qd_parse_node_t, qd_parse_node_list_t); |
| struct qd_parse_node { |
| DEQ_LINKS(qd_parse_node_t); // siblings |
| char *token; // portion of pattern represented by this node |
| char *pattern; // entire normalized pattern matching this node |
| |
| // sub-trees of this node: |
| qd_parse_node_t *match_1_child; // starts with a match 1 wildcard |
| qd_parse_node_t *match_glob_child; // starts with 0 or more wildcard |
| qd_parse_node_list_t children; // all that start with a non-wildcard token |
| |
| qd_parse_node_t *parent; // NULL if root node |
| qd_hash_handle_t *handle; |
| void *payload; |
| |
| // prefix for hash keys of this node's children |
| uint32_t hkey_prefix; |
| qd_parse_node_type_t match_type; |
| }; |
| ALLOC_DECLARE(qd_parse_node_t); |
| ALLOC_DEFINE(qd_parse_node_t); |
| |
| |
| // Hash key for a token node is in the following format: |
| // "<parent node hkey-prefix in hex>/<token> |
| // |
| #define HKEY_PREFIX_LEN (8 + 1) // 8 hex chars (32 bit integer) + '/' |
| static inline void generate_hkey(char *hkey, size_t buf_len, uint32_t pprefix, const token_t *t) |
| { |
| int rc = snprintf(hkey, buf_len, "%"PRIX32"/%.*s", pprefix, (int)TOKEN_LEN(*t), t->begin); |
| assert(rc > 0 && rc < buf_len); |
| (void)rc; // prevent warnings for unused var |
| } |
| |
| |
| // return count of child nodes |
| static int parse_node_child_count(const qd_parse_node_t *n) |
| { |
| return DEQ_SIZE(n->children) |
| + (n->match_1_child ? 1 : 0) |
| + (n->match_glob_child ? 1 : 0); |
| } |
| |
| |
| // Free a parse node |
| static void free_parse_node(qd_parse_tree_t *tree, qd_parse_node_t *n) |
| { |
| if (n) { |
| assert(parse_node_child_count(n) == 0); |
| free(n->token); |
| free(n->pattern); |
| if (n->handle) { |
| qd_hash_remove_by_handle(tree->hash, n->handle); |
| qd_hash_handle_free(n->handle); |
| } |
| free_qd_parse_node_t(n); |
| } |
| } |
| |
| |
| // Allocate a new parse tree node. |
| // |
| // Allocate a new hash node. If a token is given the hash key for this node is |
| // generated by concatenating the parent prefix with the token. |
| // |
| static qd_parse_node_t *new_parse_node(qd_parse_tree_t *tree, |
| qd_parse_node_type_t match_type, |
| const token_t *t, |
| qd_parse_node_t *parent) |
| { |
| qd_parse_node_t *n = new_qd_parse_node_t(); |
| if (n) { |
| ZERO(n); |
| DEQ_ITEM_INIT(n); |
| DEQ_INIT(n->children); |
| n->match_type = match_type; |
| // generate a new hash key prefix for this node's children |
| n->hkey_prefix = tree->next_hkey_prefix++; |
| |
| if (match_type == QD_PARSE_NODE_TOKEN) { |
| assert(t); |
| assert(parent); |
| const size_t tlen = TOKEN_LEN(*t); |
| n->token = malloc(tlen + 1); |
| if (!n->token) { |
| free_qd_parse_node_t(n); |
| return 0; |
| } |
| strncpy(n->token, t->begin, tlen); |
| n->token[tlen] = 0; |
| { |
| const size_t hkey_size = HKEY_PREFIX_LEN + tlen + 1; |
| char *hkey = qd_malloc(hkey_size); |
| generate_hkey(hkey, hkey_size, parent->hkey_prefix, t); |
| |
| if (qd_hash_insert_str(tree->hash, (unsigned char *)hkey, (void *)n, &n->handle) != QD_ERROR_NONE) { |
| free_parse_node(tree, n); |
| free(hkey); |
| return 0; |
| } |
| free(hkey); |
| n->parent = parent; |
| } |
| } |
| } |
| |
| return n; |
| } |
| |
| |
| // find immediate child node matching token |
| static qd_parse_node_t *parse_node_find_child(qd_parse_tree_t *tree, const qd_parse_node_t *node, const token_t *token) |
| { |
| qd_parse_node_t *child = 0; |
| const size_t tlen = TOKEN_LEN(*token); |
| const size_t hkey_size = HKEY_PREFIX_LEN + tlen + 1; |
| char *hkey = qd_malloc(hkey_size); |
| generate_hkey(hkey, hkey_size, node->hkey_prefix, token); |
| qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child); |
| if (child) { |
| assert(child->parent == node); |
| } |
| free(hkey); |
| return child; |
| } |
| |
| |
| // Add a new pattern and associated data to the tree. |
| // Return QD_ERROR_ALREADY_EXISTS if the pattern is already in the tree. |
| // caller transfers ownership of "pattern" |
| // |
| static qd_error_t parse_node_add_pattern(qd_parse_tree_t *tree, char *pattern, void *payload) |
| { |
| qd_parse_node_t *node = tree->root; |
| qd_error_t result = QD_ERROR_NONE; |
| token_iterator_t iterator; |
| |
| normalize_pattern(tree->type, pattern); |
| |
| // worst case maximum for hash key if pattern is a single token |
| const size_t buf_size = HKEY_PREFIX_LEN + strlen(pattern) + 1; |
| char *hkey = malloc(buf_size); |
| if (!hkey) { |
| result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern); |
| free(pattern); |
| return result; |
| } |
| |
| token_iterator_init(&iterator, tree->type, pattern); |
| |
| // |
| // walk the iterator through the tree adding nodes for each token/wildcard as needed |
| // |
| while (!token_iterator_done(&iterator)) { |
| |
| if (token_iterator_is_match_1(&iterator)) { |
| if (!node->match_1_child) { |
| node->match_1_child = new_parse_node(tree, QD_PARSE_NODE_MATCH_ONE, 0, node); |
| if (!node->match_1_child) { |
| result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern); |
| break; |
| } |
| node->match_1_child->parent = node; |
| } |
| node = node->match_1_child; |
| token_iterator_next(&iterator); |
| |
| } else if (token_iterator_is_match_glob(&iterator)) { |
| if (!node->match_glob_child) { |
| node->match_glob_child = new_parse_node(tree, QD_PARSE_NODE_MATCH_GLOB, 0, node); |
| if (!node->match_glob_child) { |
| result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern); |
| break; |
| } |
| node->match_glob_child->parent = node; |
| } |
| node = node->match_glob_child; |
| token_iterator_next(&iterator); |
| |
| } else { // non-wildcard token |
| token_t new_token; |
| token_iterator_pop(&iterator, &new_token); |
| generate_hkey(hkey, buf_size, node->hkey_prefix, &new_token); |
| |
| qd_parse_node_t *child = 0; |
| qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child); |
| |
| if (child) { |
| assert(child->parent == node); |
| node = child; |
| } else { |
| // add new node for new_token |
| child = new_parse_node(tree, QD_PARSE_NODE_TOKEN, &new_token, node); |
| if (!child) { |
| result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern); |
| break; |
| } |
| DEQ_INSERT_TAIL(node->children, child); |
| node = child; |
| } |
| } |
| } |
| |
| // done iterating over pattern. |
| |
| if (result == QD_ERROR_NONE) { |
| if (node == tree->root) { |
| result = qd_error(QD_ERROR_VALUE, "Invalid pattern '%s' not added to parse tree", pattern); |
| } else if (node->pattern) { |
| result = qd_error(QD_ERROR_ALREADY_EXISTS, "Duplicate pattern '%s' not added to parse tree", pattern); |
| } else { |
| node->pattern = pattern; |
| pattern = 0; |
| node->payload = payload; |
| qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree add pattern '%s'", node->pattern); |
| } |
| } |
| |
| free(pattern); |
| free(hkey); |
| |
| return result; |
| } |
| |
| |
| // Find the leaf node matching the pattern. Returns 0 if pattern is not in the tree |
| // |
| static qd_parse_node_t *parse_node_get_pattern(qd_parse_tree_t *tree, char *pattern) |
| { |
| qd_parse_node_t *node = tree->root; |
| token_iterator_t iterator; |
| |
| normalize_pattern(tree->type, pattern); |
| |
| // worst case maximum for hash key if pattern is a single token |
| const size_t buf_size = HKEY_PREFIX_LEN + strlen(pattern) + 1; |
| char *hkey = malloc(buf_size); |
| if (!hkey) { |
| return 0; |
| } |
| |
| token_iterator_init(&iterator, tree->type, pattern); |
| |
| while (node && !token_iterator_done(&iterator)) { |
| |
| if (token_iterator_is_match_1(&iterator)) { |
| node = node->match_1_child; |
| token_iterator_next(&iterator); |
| |
| } else if (token_iterator_is_match_glob(&iterator)) { |
| node = node->match_glob_child; |
| token_iterator_next(&iterator); |
| |
| } else { |
| token_t new_token; |
| token_iterator_pop(&iterator, &new_token); |
| |
| // search for new_token child |
| assert(node->hkey_prefix); |
| generate_hkey(hkey, buf_size, node->hkey_prefix, &new_token); |
| |
| qd_parse_node_t *child = 0; |
| qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child); |
| |
| assert(!child || child->parent == node); |
| node = child; |
| } |
| } |
| |
| free(hkey); |
| |
| if (node && node->pattern) { |
| assert(strcmp(node->pattern, pattern) == 0); |
| return node; |
| } |
| |
| return 0; |
| } |
| |
| |
| static bool parse_node_find(qd_parse_tree_t *, qd_parse_node_t *, token_iterator_t *, |
| qd_parse_tree_visit_t *, void *); |
| |
| |
| // search the sub-trees of this node. |
| // This function returns false to stop the search |
| static bool parse_node_find_children(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| if (!token_iterator_done(value)) { |
| |
| // check exact match first (precedence) |
| if (DEQ_SIZE(node->children) > 0) { |
| token_iterator_t tmp = *value; |
| token_t child_token; |
| token_iterator_pop(&tmp, &child_token); |
| |
| qd_parse_node_t *child = parse_node_find_child(tree, node, &child_token); |
| if (child) { |
| if (!parse_node_find(tree, child, &tmp, callback, handle)) |
| return false; |
| } |
| } |
| |
| if (node->match_1_child) { |
| token_iterator_t tmp = *value; |
| if (!parse_node_find(tree, node->match_1_child, &tmp, callback, handle)) |
| return false; |
| } |
| } |
| |
| // always try glob - even if empty (it can match empty keys) |
| if (node->match_glob_child) { |
| token_iterator_t tmp = *value; |
| if (!parse_node_find(tree, node->match_glob_child, &tmp, callback, handle)) |
| return false; |
| } |
| |
| return true; // continue search |
| } |
| |
| |
| // This node matched the last token. |
| // This function returns false to stop the search |
| static bool parse_node_find_token(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| if (token_iterator_done(value) && node->pattern) { |
| // exact match current node |
| if (!callback(handle, node->pattern, node->payload)) |
| return false; |
| } |
| |
| // no payload or more tokens. Continue to lower sub-trees. Even if no more |
| // tokens (may have a # match) |
| return parse_node_find_children(tree, node, value, callback, handle); |
| } |
| |
| |
| // this node matches any one token |
| // returns false to stop the search |
| static bool parse_node_match_1(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| // must match exactly one token: |
| if (token_iterator_done(value)) |
| return true; // no match here, but continue searching |
| |
| // pop the topmost token (matched) |
| token_iterator_next(value); |
| |
| if (token_iterator_done(value) && node->pattern) { |
| // exact match current node |
| if (!callback(handle, node->pattern, node->payload)) |
| return false; |
| } |
| |
| // no payload or more tokens: continue to lower sub-trees |
| return parse_node_find_children(tree, node, value, callback, handle); |
| } |
| |
| |
| // current node is hash, match the remainder of the string |
| // return false to stop search |
| static bool parse_node_match_glob(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| // consume each token and look for a match on the |
| // remaining key. |
| while (!token_iterator_done(value)) { |
| if (!parse_node_find_children(tree, node, value, callback, handle)) |
| return false; |
| token_iterator_next(value); |
| } |
| |
| // this node matches |
| if (node->pattern) |
| return callback(handle, node->pattern, node->payload); |
| |
| return true; |
| } |
| |
| |
| // find the payload associated with the input string 'value' |
| static bool parse_node_find(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| switch (node->match_type) { |
| case QD_PARSE_NODE_MATCH_ONE: |
| return parse_node_match_1(tree, node, value, callback, handle); |
| case QD_PARSE_NODE_MATCH_GLOB: |
| return parse_node_match_glob(tree, node, value, callback, handle); |
| case QD_PARSE_NODE_TOKEN: |
| return parse_node_find_token(tree, node, value, callback, handle); |
| case QD_PARSE_NODE_ROOT: |
| return parse_node_find_children(tree, node, value, callback, handle); |
| } |
| return true; |
| } |
| |
| |
| static void parse_node_free(qd_parse_tree_t *tree, qd_parse_node_t *node) |
| { |
| if (node) { |
| if (node->match_1_child) parse_node_free(tree, node->match_1_child); |
| if (node->match_glob_child) parse_node_free(tree, node->match_glob_child); |
| node->match_1_child = node->match_glob_child = NULL; |
| while (!DEQ_IS_EMPTY(node->children)) { |
| qd_parse_node_t *child = DEQ_HEAD(node->children); |
| DEQ_REMOVE_HEAD(node->children); |
| parse_node_free(tree, child); |
| } |
| |
| free_parse_node(tree, node); |
| } |
| } |
| |
| |
| qd_parse_tree_t *qd_parse_tree_new(qd_parse_tree_type_t type) |
| { |
| |
| qd_parse_tree_t *tree = new_qd_parse_tree_t(); |
| if (tree) { |
| ZERO(tree); |
| tree->type = type; |
| tree->log_source = qd_log_source("DEFAULT"); |
| tree->next_hkey_prefix = 1; |
| tree->root = new_parse_node(tree, QD_PARSE_NODE_ROOT, 0, 0); |
| if (!tree->root) { |
| free_qd_parse_tree_t(tree); |
| return 0; |
| } |
| tree->hash = qd_hash(10, 32, false); |
| if (!tree->hash) { |
| parse_node_free(tree, tree->root); |
| free_qd_parse_tree_t(tree); |
| return 0; |
| } |
| } |
| |
| return tree; |
| } |
| |
| |
| // find best match for value |
| // |
| static bool get_first(void *handle, const char *pattern, void *payload) |
| { |
| *(void **)handle = payload; |
| return false; |
| } |
| |
| bool qd_parse_tree_retrieve_match(qd_parse_tree_t *tree, |
| const qd_iterator_t *value, |
| void **payload) |
| { |
| *payload = NULL; |
| qd_parse_tree_search(tree, value, get_first, payload); |
| if (*payload == NULL) |
| qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree match not found"); |
| return *payload != NULL; |
| } |
| |
| |
| // Invoke callback for each pattern that matches 'value' |
| void qd_parse_tree_search(qd_parse_tree_t *tree, |
| const qd_iterator_t *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| token_iterator_t t_iter; |
| char *str = (char *)qd_iterator_copy_const(value); |
| qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree search for '%s'", str); |
| |
| token_iterator_init(&t_iter, tree->type, str); |
| parse_node_find(tree, tree->root, &t_iter, callback, handle); |
| |
| free(str); |
| } |
| |
| |
| qd_error_t qd_parse_tree_add_pattern(qd_parse_tree_t *tree, |
| const qd_iterator_t *pattern, |
| void *payload) |
| { |
| char *str = (char *)qd_iterator_copy_const(pattern); |
| if (!str) |
| return QD_ERROR_ALLOC; |
| |
| return parse_node_add_pattern(tree, str, payload); |
| } |
| |
| |
| // returns true if pattern exists in tree |
| bool qd_parse_tree_get_pattern(qd_parse_tree_t *tree, |
| const qd_iterator_t *pattern, |
| void **payload) |
| { |
| char *str = (char *)qd_iterator_copy_const(pattern); |
| if (!str) |
| return false; |
| |
| qd_parse_node_t *found = parse_node_get_pattern(tree, str); |
| free(str); |
| *payload = found ? found->payload : NULL; |
| return !!found; |
| } |
| |
| |
| // returns the payload void * |
| void *qd_parse_tree_remove_pattern(qd_parse_tree_t *tree, |
| const qd_iterator_t *pattern) |
| { |
| char *str = (char *)qd_iterator_copy_const(pattern); |
| if (!str) |
| return 0; |
| |
| qd_parse_node_t *node = parse_node_get_pattern(tree, str); |
| if (!node) { |
| free(str); |
| return 0; |
| } |
| |
| assert(strcmp(node->pattern, str) == 0); |
| free(str); |
| |
| void *value = node->payload; |
| |
| // now clean up this node. Be aware we can only free the entire node if it does not have children. |
| // If it has children just zero out the pattern field indicates it is no longer a terminal node. |
| // The fun happens if it has no children: in this case free the node then |
| // see if the parent node is now childless and NOT a terminal node. If so |
| // clean it up as well. Repeat. |
| |
| free(node->pattern); |
| node->pattern = 0; |
| node->payload = 0; |
| qd_parse_node_t *parent = node->parent; |
| |
| while (node && node->pattern == 0 && parse_node_child_count(node) == 0 && parent) { |
| |
| switch (node->match_type) { |
| case QD_PARSE_NODE_MATCH_ONE: |
| assert(parent->match_1_child == node); |
| parent->match_1_child = 0; |
| break; |
| case QD_PARSE_NODE_MATCH_GLOB: |
| assert(parent->match_glob_child == node); |
| parent->match_glob_child = 0; |
| break; |
| case QD_PARSE_NODE_TOKEN: |
| DEQ_REMOVE(parent->children, node); |
| break; |
| case QD_PARSE_NODE_ROOT: |
| default: |
| // cannot get here |
| assert(false); |
| break; |
| } |
| free_parse_node(tree, node); |
| node = parent; |
| parent = node->parent; |
| } |
| |
| return value; |
| } |
| |
| |
| static bool parse_tree_walk(qd_parse_node_t *node, qd_parse_tree_visit_t *callback, void *handle) |
| { |
| if (node->pattern) { // terminal node for pattern |
| if (!callback(handle, node->pattern, node->payload)) |
| return false; |
| } |
| |
| qd_parse_node_t *child = DEQ_HEAD(node->children); |
| while (child) { |
| if (!parse_tree_walk(child, callback, handle)) |
| return false; |
| child = DEQ_NEXT(child); |
| } |
| |
| if (node->match_1_child) |
| if (!parse_tree_walk(node->match_1_child, callback, handle)) |
| return false; |
| |
| if (node->match_glob_child) |
| if (!parse_tree_walk(node->match_glob_child, callback, handle)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| bool qd_parse_tree_walk(qd_parse_tree_t *tree, qd_parse_tree_visit_t *callback, void *handle) |
| { |
| return parse_tree_walk(tree->root, callback, handle); |
| } |
| |
| |
| bool qd_parse_tree_validate_pattern(const qd_parse_tree_t *tree, |
| const qd_iterator_t *pattern) |
| { |
| char *str = (char *)qd_iterator_copy_const(pattern); |
| if (!str) |
| return false; |
| |
| token_iterator_t ti; |
| token_iterator_init(&ti, tree->type, str); |
| if (token_iterator_done(&ti)) { |
| // empty iterator |
| free(str); |
| return false; |
| } |
| |
| if (tree->type == QD_PARSE_TREE_MQTT) { |
| // simply ensure that if a '#' is present it is the last token in the |
| // pattern |
| bool valid = true; |
| while (!token_iterator_done(&ti)) { |
| token_t head; |
| token_iterator_pop(&ti, &head); |
| if (token_match_str(&head, "#")) { |
| valid = token_iterator_done(&ti); |
| break; |
| } |
| } |
| free(str); |
| return valid; |
| } |
| |
| free(str); |
| return true; |
| } |
| |
| |
| qd_parse_tree_type_t qd_parse_tree_type(const qd_parse_tree_t *tree) |
| { |
| return tree->type; |
| } |
| |
| |
| static void _parse_tree_free(qd_parse_tree_t *tree) |
| { |
| if (tree) { |
| parse_node_free(tree, tree->root); |
| qd_hash_free(tree->hash); |
| free_qd_parse_tree_t(tree); |
| } |
| } |
| |
| #if 0 |
| |
| static void _node_dump(const qd_parse_node_t *node, const int depth) |
| { |
| for (int i = 0; i < depth; i++) |
| fprintf(stdout, " "); |
| char *type = (node->match_type == QD_PARSE_NODE_ROOT |
| ? "ROOT" |
| : (node->match_type == QD_PARSE_NODE_TOKEN |
| ? "TOKEN" |
| : (node->match_type == QD_PARSE_NODE_MATCH_ONE |
| ? "STAR" |
| : "GLOB"))); |
| fprintf(stdout, "token: %s pattern: %s type: %s # childs: %d hkey: %s hprefix %"PRIX32" payload: %p\n", |
| node->token ? node->token : "*NONE*", |
| node->pattern ? node->pattern: "*NONE*", |
| type, |
| parse_node_child_count(node), |
| (node->handle ? (const char *)qd_hash_key_by_handle(node->handle) : "*NONE*"), |
| node->hkey_prefix, |
| node->payload); |
| |
| if (node->match_1_child) _node_dump(node->match_1_child, depth + 1); |
| if (node->match_glob_child) _node_dump(node->match_glob_child, depth + 1); |
| qd_parse_node_t *child = DEQ_HEAD(node->children); |
| while (child) { |
| _node_dump(child, depth + 1); |
| child = DEQ_NEXT(child); |
| } |
| } |
| |
| void qd_parse_tree_dump(const qd_parse_tree_t *tree) |
| { |
| if (tree && tree->root) { |
| _node_dump(tree->root, 0); |
| fflush(stdout); |
| } |
| } |
| |
| void qd_parse_tree_free(qd_parse_tree_t *tree) |
| { |
| fprintf(stdout, "PARSE TREE DUMP\n"); |
| fprintf(stdout, "===============\n"); |
| qd_parse_tree_dump(tree); |
| _parse_tree_free(tree); |
| } |
| |
| #else |
| |
| void qd_parse_tree_free(qd_parse_tree_t *tree) |
| { |
| _parse_tree_free(tree); |
| } |
| #endif |
| |
| // |
| // parse tree functions using string interface |
| // |
| |
| qd_error_t qd_parse_tree_add_pattern_str(qd_parse_tree_t *tree, |
| const char *pattern, |
| void *payload) |
| { |
| char *str = strdup(pattern); |
| if (!str) |
| return QD_ERROR_ALLOC; |
| |
| return parse_node_add_pattern(tree, str, payload); |
| } |
| |
| |
| // visit each matching pattern that matches value in the order based on the |
| // precedence rules |
| void qd_parse_tree_search_str(qd_parse_tree_t *tree, |
| const char *value, |
| qd_parse_tree_visit_t *callback, void *handle) |
| { |
| token_iterator_t t_iter; |
| // @TODO(kgiusti) for now: |
| char *str = strdup(value); |
| qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree(str) search for '%s'", str); |
| |
| token_iterator_init(&t_iter, tree->type, str); |
| parse_node_find(tree, tree->root, &t_iter, callback, handle); |
| |
| free(str); |
| } |
| |
| |
| // returns true on match and sets *payload |
| bool qd_parse_tree_retrieve_match_str(qd_parse_tree_t *tree, |
| const char *value, |
| void **payload) |
| { |
| *payload = NULL; |
| qd_parse_tree_search_str(tree, value, get_first, payload); |
| if (*payload == NULL) |
| qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree(str) match not found"); |
| return *payload != NULL; |
| } |
| |
| // returns old payload or NULL if not present |
| void *qd_parse_tree_remove_pattern_str(qd_parse_tree_t *tree, |
| const char *pattern) |
| { |
| qd_iterator_t *piter = qd_iterator_string(pattern, ITER_VIEW_ALL); |
| void *result = qd_parse_tree_remove_pattern(tree, piter); |
| qd_iterator_free(piter); |
| return result; |
| } |