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 *);
29 disambiguate_nodelist (struct list
*, struct list
*, vector
, unsigned int);
31 /* token matcher prototypes */
32 static enum match_type
33 match_token (struct graph_node
*, char *);
35 static enum match_type
36 match_ipv4 (const char *);
38 static enum match_type
39 match_ipv4_prefix (const char *);
41 static enum match_type
42 match_ipv6 (const char *);
44 static enum match_type
45 match_ipv6_prefix (const char *);
47 static enum match_type
48 match_range (struct graph_node
*, const char *);
50 static enum match_type
51 match_word (struct graph_node
*, const char *);
53 static enum match_type
54 match_number (struct graph_node
*, const char *);
56 static enum match_type
57 match_variable (struct graph_node
*, const char *);
59 /* matching functions */
60 static enum matcher_rv matcher_result_value
;
63 match_command (struct graph_node
*start
,
66 struct cmd_element
**el
)
68 matcher_result_value
= MATCHER_NO_MATCH
;
70 vector vline
= cmd_make_strvec (line
);
72 for (unsigned int i
= 0; i
< vector_active(start
->children
); i
++)
74 // call recursive matcher on each starting child
75 *argvv
= match_command_r(vector_slot(start
->children
, i
), vline
, 0);
81 struct graph_node
*gn
;
82 for (ALL_LIST_ELEMENTS_RO(*argvv
,ln
,gn
))
83 if (gn
->type
== END_GN
) {
90 return matcher_result_value
;
94 * Builds an argument list given a DFA and a matching input line.
96 * First the function determines if the node it is passed matches the first
97 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
98 * does match, then it saves the input token as the head of an argument list.
100 * The next step is to see if there is further input in the input line. If
101 * there is not, the current node's children are searched to see if any of them
102 * are leaves (type END_GN). If this is the case, then the bottom of the
103 * recursion stack has been reached, the leaf is pushed onto the argument list,
104 * the current node is pushed, and the resulting argument list is
105 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
106 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
108 * If there is further input, then the function recurses on each of the current
109 * node's children, passing them the input line minus the token that was just
110 * matched. For each child, the return value of the recursive call is
111 * inspected. If it is null, then there is no match for the input along the
112 * subgraph headed by that child. If it is not null, then there is at least one
113 * input match in that subgraph (more on this in a moment).
115 * If a recursive call on a child returns a non-null value, then it has matched
116 * the input given it on the subgraph that starts with that child. However, due
117 * to the flexibility of the grammar, it is sometimes the case that two or more
118 * child graphs match the same input (two or more of the recursive calls have
119 * non-NULL return values). This is not a valid state, since only one true
120 * match is possible. In order to resolve this conflict, the function keeps a
121 * reference to the child node that most specifically matches the input. This
122 * is done by assigning each node type a precedence. If a child is found to
123 * match the remaining input, then the precedence values of the current
124 * best-matching child and this new match are compared. The node with higher
125 * precedence is kept, and the other match is discarded. Due to the recursive
126 * nature of this function, it is only necessary to compare the precedence of
127 * immediate children, since all subsequent children will already have been
128 * disambiguated in this way.
130 * In the event that two children are found to match with the same precedence,
131 * then the input is ambiguous for the passed cmd_element and NULL is returned.
133 * The ultimate return value is an ordered linked list of nodes that comprise
134 * the best match for the command, each with their `arg` fields pointing to the
135 * matching token string.
137 * @param[out] start the start node.
138 * @param[in] vline the vectorized input line.
139 * @param[in] n the index of the first input token. Should be 0 for external
143 match_command_r (struct graph_node
*start
, vector vline
, unsigned int n
)
145 // get the minimum match level that can count as a full match
146 enum match_type minmatch
= min_match_level(start
->type
);
148 // get the current operating token
149 char *token
= vector_slot(vline
, n
);
151 // if we don't match this node, die
152 if (match_token(start
, token
) < minmatch
)
155 // pointers for iterating linklist
156 struct graph_node
*gn
;
159 // get all possible nexthops
160 struct list
*next
= list_new();
161 add_nexthops(next
, start
);
163 // determine the best match
165 struct list
*currbest
= NULL
;
166 for (ALL_LIST_ELEMENTS_RO(next
,ln
,gn
))
168 // if we've matched all input we're looking for END_GN
169 if (n
+1 == vector_active (vline
)) {
170 if (gn
->type
== END_GN
) {
171 currbest
= list_new();
172 listnode_add(currbest
, copy_node(gn
));
173 currbest
->del
= &delete_nodelist
;
179 // else recurse on candidate child node
180 struct list
*result
= match_command_r (gn
, vline
, n
+1);
182 // save the best match, subtle logic at play here
183 if (result
&& currbest
) {
184 struct list
*newbest
= disambiguate_nodelist(currbest
, result
, vline
, n
+1);
185 ambiguous
= !newbest
|| (ambiguous
&& newbest
== currbest
);
186 list_delete ((newbest
&& newbest
== result
) ? currbest
: result
);
187 currbest
= newbest
? newbest
: currbest
;
195 list_delete(currbest
);
197 matcher_result_value
= MATCHER_AMBIGUOUS
;
200 // copy current node, set arg and prepend to currbest
201 struct graph_node
*curr
= copy_node(start
);
202 curr
->arg
= XSTRDUP(MTYPE_CMD_TOKENS
, token
);
203 list_add_node_prev (currbest
, currbest
->head
, curr
);
204 matcher_result_value
= MATCHER_OK
;
208 if (n
+1 == vector_active(vline
) && matcher_result_value
== MATCHER_NO_MATCH
)
209 matcher_result_value
= MATCHER_INCOMPLETE
;
219 match_command_complete (struct graph_node
*start
, const char *line
)
221 // vectorize command line
222 vector vline
= cmd_make_strvec (line
);
224 // pointer to next input token to match
227 struct list
*current
= list_new(), // current nodes to match input token against
228 *next
= list_new(); // possible next hops after current input token
230 // pointers used for iterating lists
231 struct graph_node
*gn
;
232 struct listnode
*node
;
234 // add all children of start node to list
235 add_nexthops(next
, start
);
238 for (idx
= 0; idx
< vector_active(vline
) && next
->count
> 0; idx
++)
244 token
= vector_slot(vline
, idx
);
246 for (ALL_LIST_ELEMENTS_RO(current
,node
,gn
))
248 switch (match_token(gn
, token
)) {
250 if (idx
== vector_active(vline
) - 1) {
251 listnode_add(next
, gn
);
255 add_nexthops(next
, gn
);
264 * -----------------------------------------------------------------
265 * token = last input token processed
266 * idx = index in `command` of last token processed
267 * current = set of all transitions from the previous input token
268 * next = set of all nodes reachable from all nodes in `matched`
271 matcher_result_value
=
272 idx
+ 1 == vector_active(vline
) && next
->count
?
277 cmd_free_strvec(vline
);
283 * Adds all children that are reachable by one parser hop
284 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
285 * nodes are treated as transparent.
287 * @param[out] l the list to add the children to
288 * @param[in] node the node to get the children of
289 * @return the number of children added to the list
292 add_nexthops(struct list
*l
, struct graph_node
*node
)
295 struct graph_node
*child
;
296 for (unsigned int i
= 0; i
< vector_active(node
->children
); i
++)
298 child
= vector_slot(node
->children
, i
);
299 switch (child
->type
) {
303 added
+= add_nexthops(l
, child
);
306 listnode_add(l
, child
);
314 * Determines the node types for which a partial match may count as a full
315 * match. Enables command abbrevations.
317 static enum match_type
318 min_match_level(enum node_type type
)
328 /* Precedence score used to disambiguate matches. */
330 score_precedence (enum graph_node_type type
)
334 // some of these are mutually exclusive, so they share
335 // the same precedence value
353 /* Disambiguation logic to pick the best of two possible matches */
354 static struct graph_node
*
355 disambiguate (struct graph_node
*first
, struct graph_node
*second
, char *token
)
357 // if the types are different, simply go off of type precedence
358 if (first
->type
!= second
->type
) {
359 int firstprec
= score_precedence(first
->type
);
360 int secndprec
= score_precedence(second
->type
);
361 if (firstprec
!= secndprec
)
362 return firstprec
< secndprec
? first
: second
;
367 // if they're the same, return the more exact match
368 enum match_type fmtype
= match_token (first
, token
);
369 enum match_type smtype
= match_token (second
, token
);
370 if (fmtype
!= smtype
)
371 return fmtype
> smtype
? first
: second
;
377 disambiguate_nodelist (struct list
*first
, struct list
*second
, vector vline
, unsigned int n
)
379 fprintf(stderr
, "%d --- %d\n", first
->count
, n
);
381 // doesn't make sense for these to be inequal length
382 assert(first
->count
== second
->count
);
383 assert(first
->count
== vector_active(vline
) - n
+1);
385 struct listnode
*fnode
= listhead(first
),
386 *snode
= listhead(second
);
387 struct graph_node
*fgn
= listgetdata(fnode
),
388 *sgn
= listgetdata(snode
),
391 // compare each node, if one matches better use that one
392 for (unsigned int i
= n
; i
< vector_active(vline
); i
++) {
393 if ((best
= disambiguate (fgn
, sgn
, (char*) vector_slot(vline
, i
))))
394 return best
== fgn
? first
: second
;
395 fnode
= listnextnode(fnode
);
396 fgn
= (struct graph_node
*) listgetdata (fnode
);
397 snode
= listnextnode(snode
);
398 sgn
= (struct graph_node
*) listgetdata (snode
);
404 static struct graph_node
*
405 copy_node (struct graph_node
*node
)
407 struct graph_node
*new = new_node(node
->type
);
408 new->children
= NULL
;
409 new->is_start
= node
->is_start
;
411 new->text
= node
->text
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->text
) : NULL
;
412 new->value
= node
->value
;
413 new->min
= node
->min
;
414 new->max
= node
->max
;
415 new->element
= node
->element
? copy_cmd_element(node
->element
) : NULL
;
416 new->arg
= node
->arg
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->arg
) : NULL
;
421 /* Linked list data deletion callback */
423 delete_nodelist (void *node
)
425 free_node ((struct graph_node
*) node
);
429 /* token level matching functions */
431 static enum match_type
432 match_token (struct graph_node
*node
, char *token
)
434 switch (node
->type
) {
436 return match_word (node
, token
);
438 return match_ipv4 (token
);
440 return match_ipv4_prefix (token
);
442 return match_ipv6 (token
);
444 return match_ipv6_prefix (token
);
446 return match_range (node
, token
);
448 return match_number (node
, token
);
450 return match_variable (node
, token
);
457 #define IPV4_ADDR_STR "0123456789."
458 #define IPV4_PREFIX_STR "0123456789./"
460 static enum match_type
461 match_ipv4 (const char *str
)
464 int dots
= 0, nums
= 0;
472 memset (buf
, 0, sizeof (buf
));
481 if (*(str
+ 1) == '.')
484 if (*(str
+ 1) == '\0')
490 if (!isdigit ((int) *str
))
499 strncpy (buf
, sp
, str
- sp
);
500 if (atoi (buf
) > 255)
517 static enum match_type
518 match_ipv4_prefix (const char *str
)
529 memset (buf
, 0, sizeof (buf
));
531 while (*str
!= '\0' && *str
!= '/')
538 if (*(str
+ 1) == '.' || *(str
+ 1) == '/')
541 if (*(str
+ 1) == '\0')
548 if (!isdigit ((int) *str
))
557 strncpy (buf
, sp
, str
- sp
);
558 if (atoi (buf
) > 255)
565 if (*(str
+ 1) == '\0')
571 else if (*str
== '\0')
584 if (!isdigit ((int) *str
))
597 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
598 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
600 static enum match_type
601 match_ipv6 (const char *str
)
603 struct sockaddr_in6 sin6_dummy
;
609 if (strspn (str
, IPV6_ADDR_STR
) != strlen (str
))
612 ret
= inet_pton(AF_INET6
, str
, &sin6_dummy
.sin6_addr
);
620 static enum match_type
621 match_ipv6_prefix (const char *str
)
623 struct sockaddr_in6 sin6_dummy
;
624 const char *delim
= "/\0";
625 char *dupe
, *prefix
, *mask
, *context
, *endptr
;
631 if (strspn (str
, IPV6_PREFIX_STR
) != strlen (str
))
634 /* tokenize to address + mask */
635 dupe
= XMALLOC(MTYPE_TMP
, strlen(str
)+1);
636 strncpy(dupe
, str
, strlen(str
)+1);
637 prefix
= strtok_r(dupe
, delim
, &context
);
638 mask
= strtok_r(NULL
, delim
, &context
);
643 /* validate prefix */
644 if (inet_pton(AF_INET6
, prefix
, &sin6_dummy
.sin6_addr
) != 1)
648 nmask
= strtoimax (mask
, &endptr
, 10);
649 if (*endptr
!= '\0' || nmask
< 0 || nmask
> 128)
652 XFREE(MTYPE_TMP
, dupe
);
658 static enum match_type
659 match_range (struct graph_node
*rangenode
, const char *str
)
667 val
= strtoll (str
, &endptr
, 10);
671 if (val
< rangenode
->min
|| val
> rangenode
->max
)
677 static enum match_type
678 match_word(struct graph_node
*wordnode
, const char *word
)
680 // if the passed token is null or 0 length, partly match
681 if (!word
|| !strlen(word
))
684 // if the passed token is strictly a prefix of the full word, partly match
685 if (strlen(word
) < strlen(wordnode
->text
))
686 return !strncmp(wordnode
->text
, word
, strlen(word
)) ? partly_match
: no_match
;
688 // if they are the same length and exactly equal, exact match
689 else if (strlen(word
) == strlen(wordnode
->text
))
690 return !strncmp(wordnode
->text
, word
, strlen(word
)) ? exact_match
: no_match
;
695 static enum match_type
696 match_number(struct graph_node
*numnode
, const char *word
)
698 if (!strcmp("\0", word
)) return no_match
;
700 long long num
= strtoll (word
, &endptr
, 10);
701 if (endptr
!= '\0') return no_match
;
702 return num
== numnode
->value
? exact_match
: no_match
;
705 #define VARIABLE_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:"
707 static enum match_type
708 match_variable(struct graph_node
*varnode
, const char *word
)
710 return strlen(word
) == strspn(word
, VARIABLE_ALPHABET
) ?
711 exact_match
: no_match
;