1 #include "command_match.h"
2 #include "command_parse.h"
6 /* matcher helper prototypes */
8 add_nexthops(struct list
*, struct graph_node
*);
11 match_command_r (struct graph_node
*, vector
, unsigned int);
14 score_precedence (enum graph_node_type
);
16 static enum match_type
17 min_match_level(enum node_type
);
19 static struct graph_node
*
20 copy_node (struct graph_node
*);
23 delete_nodelist (void *);
25 static struct graph_node
*
26 disambiguate (struct graph_node
*, struct graph_node
*, char *);
28 /* token matcher prototypes */
29 static enum match_type
30 match_token (struct graph_node
*, char *);
32 static enum match_type
33 match_ipv4 (const char *);
35 static enum match_type
36 match_ipv4_prefix (const char *);
38 static enum match_type
39 match_ipv6 (const char *);
41 static enum match_type
42 match_ipv6_prefix (const char *);
44 static enum match_type
45 match_range (struct graph_node
*, const char *);
47 static enum match_type
48 match_word (struct graph_node
*, const char *);
50 static enum match_type
51 match_number (struct graph_node
*, const char *);
53 static enum match_type
54 match_variable (struct graph_node
*, const char *);
56 /* matching functions */
57 static enum matcher_rv matcher_result_value
;
60 match_command (struct graph_node
*start
,
63 struct cmd_element
**el
)
65 matcher_result_value
= MATCHER_NO_MATCH
;
67 vector vline
= cmd_make_strvec (line
);
69 for (unsigned int i
= 0; i
< vector_active(start
->children
); i
++)
71 // call recursive matcher on each starting child
72 *argvv
= match_command_r(vector_slot(start
->children
, i
), vline
, 0);
78 struct graph_node
*gn
;
79 for (ALL_LIST_ELEMENTS_RO(*argvv
,ln
,gn
))
80 if (gn
->type
== END_GN
) {
87 return matcher_result_value
;
91 * Builds an argument list given a DFA and a matching input line.
93 * First the function determines if the node it is passed matches the first
94 * token of input. If it does not, it returns NULL. If it does match, then it
95 * saves the input token as the head of an argument list.
97 * The next step is to see if there is further input in the input line. If
98 * there is not, the current node's children are searched to see if any of them
99 * are leaves (type END_GN). If this is the case, then the bottom of the
100 * recursion stack has been reached, and the argument list (with one node) is
101 * returned. If it is not the case, NULL is returned, indicating that there is
102 * no match for the input along this path.
104 * If there is further input, then the function recurses on each of the current
105 * node's children, passing them the input line minus the token that was just
106 * matched. For each child, the return value of the recursive call is
107 * inspected. If it is null, then there is no match for the input along the
108 * subgraph headed by that child. If it is not null, then there is at least one
109 * input match in that subgraph (more on this in a moment).
111 * If a recursive call on a child returns a non-null value, then it has matched
112 * the input given it on the subgraph that starts with that child. However, due
113 * to the flexibility of the grammar, it is sometimes the case that two or more
114 * child graphs match the same input (two or more of the recursive calls have
115 * non-NULL return values). This is not a valid state, since only one true
116 * match is possible. In order to resolve this conflict, the function keeps a
117 * reference to the child node that most specifically matches the input. This
118 * is done by assigning each node type a precedence. If a child is found to
119 * match the remaining input, then the precedence values of the current
120 * best-matching child and this new match are compared. The node with higher
121 * precedence is kept, and the other match is discarded. Due to the recursive
122 * nature of this function, it is only necessary to compare the precedence of
123 * immediate children, since all subsequent children will already have been
124 * disambiguated in this way.
126 * In the event that two children are found to match with the same precedence,
127 * then the input is ambiguous for the passed cmd_element and NULL is returned.
129 * The ultimate return value is an ordered linked list of nodes that comprise
130 * the best match for the command, each with their `arg` fields pointing to the
131 * matching token string.
133 * @param[out] start the start node.
134 * @param[in] vline the vectorized input line.
135 * @param[in] n the index of the first input token. Should be 0 for external
139 match_command_r (struct graph_node
*start
, vector vline
, unsigned int n
)
141 // get the minimum match level that can count as a full match
142 enum match_type minmatch
= min_match_level(start
->type
);
144 // get the current operating token
145 char *token
= vector_slot(vline
, n
);
147 // if we don't match this node, die
148 if (match_token(start
, token
) < minmatch
)
151 // pointers for iterating linklist
152 struct graph_node
*gn
;
155 // get all possible nexthops
156 struct list
*next
= list_new();
157 add_nexthops(next
, start
);
159 // determine the best match
161 struct list
*bestmatch
= NULL
;
162 for (ALL_LIST_ELEMENTS_RO(next
,ln
,gn
))
164 // if we've matched all input we're looking for END_GN
165 if (n
+1 == vector_active (vline
)) {
166 if (gn
->type
== END_GN
) {
167 bestmatch
= list_new();
168 listnode_add(bestmatch
, copy_node(gn
));
169 bestmatch
->del
= &delete_nodelist
;
175 // else recurse on candidate child node
176 struct list
*result
= match_command_r (gn
, vline
, n
+1);
178 // save the best match, subtle logic at play here
181 struct list
*to_delete
= result
;
182 struct graph_node
*new = listgetdata(result
->head
),
183 *old
= listgetdata(bestmatch
->head
);
184 char *nextoken
= vector_slot (vline
, n
+1);
185 struct graph_node
*best
= disambiguate(new, old
, nextoken
);
186 ambiguous
= !best
|| (ambiguous
&& best
== old
);
188 to_delete
= bestmatch
;
191 list_delete (to_delete
);
200 list_delete(bestmatch
);
202 matcher_result_value
= MATCHER_AMBIGUOUS
;
205 // copy current node, set arg and prepend to bestmatch
206 struct graph_node
*curr
= copy_node(start
);
207 curr
->arg
= XSTRDUP(MTYPE_CMD_TOKENS
, token
);
208 list_add_node_prev (bestmatch
, bestmatch
->head
, curr
);
209 matcher_result_value
= MATCHER_OK
;
213 if (n
+1 == vector_active(vline
) && matcher_result_value
== MATCHER_NO_MATCH
)
214 matcher_result_value
= MATCHER_INCOMPLETE
;
224 match_command_complete (struct graph_node
*start
, const char *line
)
226 // vectorize command line
227 vector vline
= cmd_make_strvec (line
);
229 // pointer to next input token to match
232 struct list
*current
= list_new(), // current nodes to match input token against
233 *next
= list_new(); // possible next hops after current input token
235 // pointers used for iterating lists
236 struct graph_node
*gn
;
237 struct listnode
*node
;
239 // add all children of start node to list
240 add_nexthops(next
, start
);
243 for (idx
= 0; idx
< vector_active(vline
) && next
->count
> 0; idx
++)
249 token
= vector_slot(vline
, idx
);
251 for (ALL_LIST_ELEMENTS_RO(current
,node
,gn
))
253 switch (match_token(gn
, token
)) {
255 if (idx
== vector_active(vline
) - 1) {
256 listnode_add(next
, gn
);
260 add_nexthops(next
, gn
);
269 * -----------------------------------------------------------------
270 * token = last input token processed
271 * idx = index in `command` of last token processed
272 * current = set of all transitions from the previous input token
273 * next = set of all nodes reachable from all nodes in `matched`
276 matcher_result_value
=
277 idx
+ 1 == vector_active(vline
) && next
->count
?
282 cmd_free_strvec(vline
);
288 * Adds all children that are reachable by one parser hop
289 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
290 * nodes are treated as transparent.
292 * @param[out] l the list to add the children to
293 * @param[in] node the node to get the children of
294 * @return the number of children added to the list
297 add_nexthops(struct list
*l
, struct graph_node
*node
)
300 struct graph_node
*child
;
301 for (unsigned int i
= 0; i
< vector_active(node
->children
); i
++)
303 child
= vector_slot(node
->children
, i
);
304 switch (child
->type
) {
308 added
+= add_nexthops(l
, child
);
311 listnode_add(l
, child
);
319 * Determines the node types for which a partial match may count as a full
320 * match. Enables command abbrevations.
322 static enum match_type
323 min_match_level(enum node_type type
)
333 /* Precedence score used to disambiguate matches. */
335 score_precedence (enum graph_node_type type
)
339 // some of these are mutually exclusive, so they share
340 // the same precedence value
358 /* Disambiguation logic to pick the best of two possible matches */
359 static struct graph_node
*
360 disambiguate (struct graph_node
*first
, struct graph_node
*second
, char *token
)
362 // if the types are different, simply go off of type precedence
363 if (first
->type
!= second
->type
) {
364 int firstprec
= score_precedence(first
->type
);
365 int secndprec
= score_precedence(second
->type
);
366 if (firstprec
!= secndprec
)
367 return firstprec
< secndprec
? first
: second
;
372 // if they're the same, return the more exact match
373 enum match_type fmtype
= match_token (first
, token
);
374 enum match_type smtype
= match_token (second
, token
);
375 if (fmtype
!= smtype
)
376 return fmtype
> smtype
? first
: second
;
381 static struct graph_node
*
382 copy_node (struct graph_node
*node
)
384 struct graph_node
*new = new_node(node
->type
);
385 new->children
= NULL
;
386 new->is_start
= node
->is_start
;
388 new->text
= node
->text
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->text
) : NULL
;
389 new->value
= node
->value
;
390 new->min
= node
->min
;
391 new->max
= node
->max
;
392 new->element
= node
->element
? copy_cmd_element(node
->element
) : NULL
;
393 new->arg
= node
->arg
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->arg
) : NULL
;
398 /* Linked list data deletion callback */
400 delete_nodelist (void *node
)
402 free_node ((struct graph_node
*) node
);
406 /* token level matching functions */
408 static enum match_type
409 match_token (struct graph_node
*node
, char *token
)
411 switch (node
->type
) {
413 return match_word (node
, token
);
415 return match_ipv4 (token
);
417 return match_ipv4_prefix (token
);
419 return match_ipv6 (token
);
421 return match_ipv6_prefix (token
);
423 return match_range (node
, token
);
425 return match_number (node
, token
);
427 return match_variable (node
, token
);
434 #define IPV4_ADDR_STR "0123456789."
435 #define IPV4_PREFIX_STR "0123456789./"
437 static enum match_type
438 match_ipv4 (const char *str
)
441 int dots
= 0, nums
= 0;
449 memset (buf
, 0, sizeof (buf
));
458 if (*(str
+ 1) == '.')
461 if (*(str
+ 1) == '\0')
467 if (!isdigit ((int) *str
))
476 strncpy (buf
, sp
, str
- sp
);
477 if (atoi (buf
) > 255)
494 static enum match_type
495 match_ipv4_prefix (const char *str
)
506 memset (buf
, 0, sizeof (buf
));
508 while (*str
!= '\0' && *str
!= '/')
515 if (*(str
+ 1) == '.' || *(str
+ 1) == '/')
518 if (*(str
+ 1) == '\0')
525 if (!isdigit ((int) *str
))
534 strncpy (buf
, sp
, str
- sp
);
535 if (atoi (buf
) > 255)
542 if (*(str
+ 1) == '\0')
548 else if (*str
== '\0')
561 if (!isdigit ((int) *str
))
574 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
575 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
577 static enum match_type
578 match_ipv6 (const char *str
)
580 struct sockaddr_in6 sin6_dummy
;
586 if (strspn (str
, IPV6_ADDR_STR
) != strlen (str
))
589 ret
= inet_pton(AF_INET6
, str
, &sin6_dummy
.sin6_addr
);
597 static enum match_type
598 match_ipv6_prefix (const char *str
)
600 struct sockaddr_in6 sin6_dummy
;
601 const char *delim
= "/\0";
602 char *dupe
, *prefix
, *mask
, *context
, *endptr
;
608 if (strspn (str
, IPV6_PREFIX_STR
) != strlen (str
))
611 /* tokenize to address + mask */
612 dupe
= XMALLOC(MTYPE_TMP
, strlen(str
)+1);
613 strncpy(dupe
, str
, strlen(str
)+1);
614 prefix
= strtok_r(dupe
, delim
, &context
);
615 mask
= strtok_r(NULL
, delim
, &context
);
620 /* validate prefix */
621 if (inet_pton(AF_INET6
, prefix
, &sin6_dummy
.sin6_addr
) != 1)
625 nmask
= strtoimax (mask
, &endptr
, 10);
626 if (*endptr
!= '\0' || nmask
< 0 || nmask
> 128)
629 XFREE(MTYPE_TMP
, dupe
);
635 static enum match_type
636 match_range (struct graph_node
*rangenode
, const char *str
)
644 val
= strtoll (str
, &endptr
, 10);
648 if (val
< rangenode
->min
|| val
> rangenode
->max
)
654 static enum match_type
655 match_word(struct graph_node
*wordnode
, const char *word
)
657 // if the passed token is null or 0 length, partly match
658 if (!word
|| !strlen(word
))
661 // if the passed token is strictly a prefix of the full word, partly match
662 if (strlen(word
) < strlen(wordnode
->text
))
663 return !strncmp(wordnode
->text
, word
, strlen(word
)) ? partly_match
: no_match
;
665 // if they are the same length and exactly equal, exact match
666 else if (strlen(word
) == strlen(wordnode
->text
))
667 return !strncmp(wordnode
->text
, word
, strlen(word
)) ? exact_match
: no_match
;
672 static enum match_type
673 match_number(struct graph_node
*numnode
, const char *word
)
675 if (!strcmp("\0", word
)) return no_match
;
677 long long num
= strtoll (word
, &endptr
, 10);
678 if (endptr
!= '\0') return no_match
;
679 return num
== numnode
->value
? exact_match
: no_match
;
682 #define VARIABLE_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:"
684 static enum match_type
685 match_variable(struct graph_node
*varnode
, const char *word
)
687 return strlen(word
) == strspn(word
, VARIABLE_ALPHABET
) ?
688 exact_match
: no_match
;