]> git.proxmox.com Git - mirror_frr.git/blame - lib/command_match.c
lib: Reorganize some matching stuff
[mirror_frr.git] / lib / command_match.c
CommitLineData
9d0662e0
QY
1#include <zebra.h>
2#include "memory.h"
18be0e59 3#include "vector.h"
9d0662e0
QY
4#include "command_match.h"
5
880e24a1
QY
6/* prototypes */
7static int
8add_nexthops(struct list *, struct graph_node *);
18be0e59 9
880e24a1
QY
10static enum match_type
11cmd_ipv4_match (const char *);
18be0e59 12
880e24a1
QY
13static enum match_type
14cmd_ipv4_prefix_match (const char *);
18be0e59 15
880e24a1
QY
16static enum match_type
17cmd_ipv6_match (const char *);
18be0e59 18
880e24a1
QY
19static enum match_type
20cmd_ipv6_prefix_match (const char *);
18be0e59 21
880e24a1
QY
22static enum match_type
23cmd_range_match (struct graph_node *, const char *str);
18be0e59 24
880e24a1
QY
25static enum match_type
26cmd_word_match (struct graph_node *, enum filter_type, const char *);
18be0e59 27
880e24a1
QY
28static vector
29cmd_make_strvec (const char *);
18be0e59 30
880e24a1
QY
31static enum match_type
32match_token (struct graph_node *, char *, enum filter_type);
18be0e59 33
880e24a1
QY
34static vector
35cmd_make_strvec (const char *);
18be0e59 36
880e24a1 37/* matching functions */
18be0e59
QY
38
39/**
40 * Compiles matches or next-hops for a given line of user input.
41 *
42 * Given a string of input and a start node for a matching DFA, runs the input
880e24a1 43 * against the DFA until the input is exhausted or a mismatch is encountered.
18be0e59 44 *
880e24a1
QY
45 * This function returns all valid next hops away from the current node.
46 * - If the input is a valid prefix to a longer command(s), the set of next
47 * hops determines what tokens are valid to follow the prefix. In other words,
48 * the returned list is a list of possible completions.
49 * - If the input matched a full command, exactly one of the next hops will be
50 * a node of type END_GN and its function pointer will be set.
51 * - If the input did not match any valid token sequence, the returned list
52 * will be empty (there are no transitions away from a nonexistent state).
18be0e59
QY
53 *
54 * @param[in] start the start node of the DFA to match against
55 * @param[in] filter the filtering method
56 * @param[in] input the input string
880e24a1
QY
57 * @return pointer to linked list with all possible next hops from the last
58 * matched token. If this is empty, the input did not match any command.
18be0e59 59 */
880e24a1 60struct list *
9d0662e0
QY
61match_command (struct graph_node *start, enum filter_type filter, const char *input)
62{
18be0e59
QY
63 // break command
64 vector command = cmd_make_strvec (input);
65
66 // pointer to next input token to match
67 char *token;
68
69 struct list *current = list_new(), // current nodes to match input token against
70 *matched = list_new(), // current nodes that match the input token
71 *next = list_new(); // possible next hops to current input token
72
73 // pointers used for iterating lists
74 struct graph_node *cnode;
75 struct listnode *node;
76
77 // add all children of start node to list
78 add_nexthops(next, start);
79
80 unsigned int idx;
81 for (idx = 0; idx < vector_active(command) && next->count > 0; idx++)
82 {
83 list_free (current);
84 current = next;
85 next = list_new();
86
87 token = vector_slot(command, idx);
88
89 list_delete_all_node(matched);
90
91 for (ALL_LIST_ELEMENTS_RO(current,node,cnode))
92 {
93 if (match_token(cnode, token, filter) == exact_match) {
94 listnode_add(matched, cnode);
95 add_nexthops(next, cnode);
96 }
97 }
98 }
99
100 /* Variable summary
101 * -----------------------------------------------------------------
102 * token = last input token processed
103 * idx = index in `command` of last token processed
104 * current = set of all transitions from the previous input token
105 * matched = set of all nodes reachable with current input
106 * next = set of all nodes reachable from all nodes in `matched`
107 */
880e24a1
QY
108 list_free (current);
109 list_free (matched);
110
111 return next;
112}
113
114/**
115 * Adds all children that are reachable by one parser hop
116 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
117 * nodes are treated as though their children are attached
118 * to their parent.
119 *
120 * @param[out] l the list to add the children to
121 * @param[in] node the node to get the children of
122 * @return the number of children added to the list
123 */
124static int
125add_nexthops(struct list *l, struct graph_node *node)
126{
127 int added = 0;
128 struct graph_node *child;
129 for (unsigned int i = 0; i < vector_active(node->children); i++)
130 {
131 child = vector_slot(node->children, i);
132 if (child->type == OPTION_GN || child->type == SELECTOR_GN || child->type == NUL_GN)
133 added += add_nexthops(l, child);
134 else {
135 listnode_add(l, child);
136 added++;
137 }
138 }
139 return added;
140}
18be0e59 141
18be0e59 142
880e24a1
QY
143/* matching utility functions */
144
145static enum match_type
146match_token (struct graph_node *node, char *token, enum filter_type filter)
147{
148 switch (node->type) {
149 case WORD_GN:
150 return cmd_word_match (node, filter, token);
151 case IPV4_GN:
152 return cmd_ipv4_match (token);
153 case IPV4_PREFIX_GN:
154 return cmd_ipv4_prefix_match (token);
155 case IPV6_GN:
156 return cmd_ipv6_match (token);
157 case IPV6_PREFIX_GN:
158 return cmd_ipv6_prefix_match (token);
159 case RANGE_GN:
160 return cmd_range_match (node, token);
161 case NUMBER_GN:
162 return node->value == atoi(token);
163 case NUL_GN:
164 return exact_match;
165 case VARIABLE_GN:
166 default:
167 return no_match;
168 }
9d0662e0
QY
169}
170
880e24a1
QY
171static vector
172cmd_make_strvec (const char *string)
173{
174 const char *cp, *start;
175 char *token;
176 int strlen;
177 vector strvec;
178
179 if (string == NULL)
180 return NULL;
181
182 cp = string;
183
184 /* Skip white spaces. */
185 while (isspace ((int) *cp) && *cp != '\0')
186 cp++;
187
188 /* Return if there is only white spaces */
189 if (*cp == '\0')
190 return NULL;
191
192 if (*cp == '!' || *cp == '#')
193 return NULL;
194
195 /* Prepare return vector. */
196 strvec = vector_init (VECTOR_MIN_SIZE);
197
198 /* Copy each command piece and set into vector. */
199 while (1)
200 {
201 start = cp;
202 while (!(isspace ((int) *cp) || *cp == '\r' || *cp == '\n') &&
203 *cp != '\0')
204 cp++;
205 strlen = cp - start;
206 token = XMALLOC (MTYPE_STRVEC, strlen + 1);
207 memcpy (token, start, strlen);
208 *(token + strlen) = '\0';
209 vector_set (strvec, token);
210
211 while ((isspace ((int) *cp) || *cp == '\n' || *cp == '\r') &&
212 *cp != '\0')
213 cp++;
214
215 if (*cp == '\0')
216 return strvec;
217 }
218}
9d0662e0
QY
219
220#define IPV4_ADDR_STR "0123456789."
221#define IPV4_PREFIX_STR "0123456789./"
222
880e24a1 223static enum match_type
9d0662e0
QY
224cmd_ipv4_match (const char *str)
225{
226 struct sockaddr_in sin_dummy;
227
228 if (str == NULL)
229 return partly_match;
230
231 if (strspn (str, IPV4_ADDR_STR) != strlen (str))
232 return no_match;
233
234 if (inet_pton(AF_INET, str, &sin_dummy.sin_addr) != 1)
235 return no_match;
236
237 return exact_match;
238}
239
880e24a1 240static enum match_type
9d0662e0
QY
241cmd_ipv4_prefix_match (const char *str)
242{
243 struct sockaddr_in sin_dummy;
244 const char *delim = "/\0";
245 char *dupe, *prefix, *mask, *context, *endptr;
246 int nmask = -1;
247
248 if (str == NULL)
249 return partly_match;
250
251 if (strspn (str, IPV4_PREFIX_STR) != strlen (str))
252 return no_match;
253
254 /* tokenize to address + mask */
255 dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
256 strncpy(dupe, str, strlen(str)+1);
257 prefix = strtok_r(dupe, delim, &context);
258 mask = strtok_r(NULL, delim, &context);
259
260 if (!mask)
261 return partly_match;
262
263 /* validate prefix */
264 if (inet_pton(AF_INET, prefix, &sin_dummy.sin_addr) != 1)
265 return no_match;
266
267 /* validate mask */
268 nmask = strtol (mask, &endptr, 10);
269 if (*endptr != '\0' || nmask < 0 || nmask > 32)
270 return no_match;
271
272 XFREE(MTYPE_TMP, dupe);
273
274 return exact_match;
275}
276
277#define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
278#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
279
280#ifdef HAVE_IPV6
880e24a1 281static enum match_type
9d0662e0
QY
282cmd_ipv6_match (const char *str)
283{
284 struct sockaddr_in6 sin6_dummy;
285 int ret;
286
287 if (str == NULL)
288 return partly_match;
289
290 if (strspn (str, IPV6_ADDR_STR) != strlen (str))
291 return no_match;
292
293 ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
294
295 if (ret == 1)
296 return exact_match;
297
298 return no_match;
299}
300
880e24a1 301static enum match_type
9d0662e0
QY
302cmd_ipv6_prefix_match (const char *str)
303{
304 struct sockaddr_in6 sin6_dummy;
305 const char *delim = "/\0";
306 char *dupe, *prefix, *mask, *context, *endptr;
307 int nmask = -1;
308
309 if (str == NULL)
310 return partly_match;
311
312 if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
313 return no_match;
314
315 /* tokenize to address + mask */
316 dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
317 strncpy(dupe, str, strlen(str)+1);
318 prefix = strtok_r(dupe, delim, &context);
319 mask = strtok_r(NULL, delim, &context);
320
321 if (!mask)
322 return partly_match;
323
324 /* validate prefix */
325 if (inet_pton(AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
326 return no_match;
327
328 /* validate mask */
329 nmask = strtol (mask, &endptr, 10);
330 if (*endptr != '\0' || nmask < 0 || nmask > 128)
331 return no_match;
332
333 XFREE(MTYPE_TMP, dupe);
334
335 return exact_match;
336}
337#endif
338
880e24a1 339static enum match_type
9d0662e0
QY
340cmd_range_match (struct graph_node *rangenode, const char *str)
341{
342 char *endptr = NULL;
343 signed long long val;
344
345 if (str == NULL)
346 return 1;
347
348 val = strtoll (str, &endptr, 10);
349 if (*endptr != '\0')
350 return 0;
351 val = llabs(val);
352
353 if (val < rangenode->min || val > rangenode->max)
354 return no_match;
355 else
356 return exact_match;
357}
358
880e24a1 359static enum match_type
9d0662e0
QY
360cmd_word_match(struct graph_node *wordnode,
361 enum filter_type filter,
362 const char *word)
363{
364 if (filter == FILTER_RELAXED)
365 if (!word || !strlen(word))
366 return partly_match;
367
368 if (!word)
369 return no_match;
370
371 if (filter == FILTER_RELAXED && !strncmp(wordnode->text, word, strlen(word)))
372 {
373 if (!strcmp(wordnode->text, word))
374 return exact_match;
375 return partly_match;
376 }
377 if (filter == FILTER_STRICT && !strcmp(wordnode->text, word))
378 return exact_match;
379
380 return no_match;
381}