summaryrefslogtreecommitdiffstats
path: root/lib/command_match.c
blob: 7b9c5f15d89c71f52b66dad79d5fded2d41cdb89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
#include "command_match.h"
#include "command_parse.h"
#include <zebra.h>
#include "memory.h"

/* matcher helper prototypes */
static int
add_nexthops(struct list *, struct graph_node *);

static struct list *
match_command_r (struct graph_node *, vector, unsigned int);

static int
score_precedence (struct graph_node *);

static enum match_type
min_match_level(enum node_type type);

/* token matcher prototypes */
static enum match_type
match_ipv4 (const char *);

static enum match_type
match_ipv4_prefix (const char *);

static enum match_type
match_ipv6 (const char *);

static enum match_type
match_ipv6_prefix (const char *);

static enum match_type
match_range (struct graph_node *, const char *str);

static enum match_type
match_word (struct graph_node *, const char *, enum filter_type);

static enum match_type
match_number (struct graph_node *, const char *);

static enum match_type
match_variable (struct graph_node *, const char *);

static enum match_type
match_token (struct graph_node *, char *, enum filter_type);

/* matching functions */

/* Linked list data deletion callback */
static void
free_nodelist (void *node) {
  free_node ((struct graph_node *) node);
}

struct cmd_element *
match_command (struct graph_node *start, const char *line, struct list **argv)
{
  // parse command
  vector vline = cmd_make_strvec (line);

  for (unsigned int i = 0; i < vector_active(start->children); i++)
  {
    // call recursive builder on each starting child
    *argv = match_command_r(vector_slot(start->children, i), vline, 0);
    // if any of them succeed, return their argv
    // since all command DFA's must begin with a word, there can only be
    // one valid return value
    if (*argv) break;
  }

  if (*argv) {
    // copy the nodes we need
    struct listnode *ln;
    struct graph_node *gn;
    char buf[50];
    for (ALL_LIST_ELEMENTS_RO(*argv,ln,gn)) {
      describe_node(gn, buf, 50);
      fprintf(stderr, "%s[%d]\n", buf, gn->type);
      if (gn->type == END_GN)
        return gn->element;
    }
    assert(0);
  }

  return NULL;
}

/**
 * Matches a given input line against a DFA.
 *
 * Builds an argument list given a DFA and a matching input line. This function
 * should be passed the start node of the DFA, a matching input line, and the
 * index of the first token in the input line.
 *
 * First the function determines if the node it is passed matches the first
 * token of input. If it does not, it returns NULL. If it does match, then it
 * saves the input token as the head of an argument list.
 *
 * The next step is to see if there is further input in the input line. If
 * there is not, the current node's children are searched to see if any of them
 * are leaves (type END_GN). If this is the case, then the bottom of the
 * recursion stack has been reached, and the argument list (with one node) is
 * returned. If it is not the case, NULL is returned, indicating that there is
 * no match for the input along this path.
 *
 * If there is further input, then the function recurses on each of the current
 * node's children, passing them the input line minus the token that was just
 * matched. For each child, the return value of the recursive call is
 * inspected. If it is null, then there is no match for the input along the
 * subgraph headed by that child. If it is not null, then there is at least one
 * input match in that subgraph (more on this in a moment).
 *
 * If a recursive call on a child returns a non-null value, then it has matched
 * the input given it on the subgraph that starts with that child. However, due
 * to the flexibility of the grammar, it is sometimes the case that two or more
 * child graphs match the same input (two or more of the recursive calls have
 * non-NULL return values). This is not a valid state, since only one true
 * match is possible. In order to resolve this conflict, the function keeps a
 * reference to the child node that most specifically matches the input. This
 * is done by assigning each node type a precedence. If a child is found to
 * match the remaining input, then the precedence values of the current
 * best-matching child and this new match are compared. The node with higher
 * precedence is kept, and the other match is discarded. Due to the recursive
 * nature of this function, it is only necessary to compare the precedence of
 * immediate children, since all subsequent children will already have been
 * disambiguated in this way.
 *
 * In the event that two children are found to match with the same precedence,
 * then the input is ambiguous for the passed cmd_element and NULL is returned.
 *
 * The ultimate return value is an ordered linked list of nodes that comprise
 * the best match for the command, each with their `arg` fields pointing to the
 * matching token string.
 *
 * @param[out] start the start node.
 * @param[in] vline the vectorized input line.
 * @param[in] n the index of the first input token. Should be 0 for external
 * callers.
 */
static struct list *
match_command_r (struct graph_node *start, vector vline, unsigned int n)
{
  // get the minimum match level that can count as a full match
  enum match_type minmatch = min_match_level(start->type);

  // if we don't match this node, die
  if (match_token(start, vector_slot(vline, n), FILTER_RELAXED) < minmatch)
    return NULL;

  // arg list for this subgraph
  struct list *argv;

  // pointers for iterating linklist
  struct graph_node *gn;
  struct listnode   *ln;

  // get all possible nexthops
  struct list *next = list_new();
  add_nexthops(next, start);

  // if we're at the end of input, need END_GN or no match
  if (n+1 == vector_active (vline)) {
    for (ALL_LIST_ELEMENTS_RO(next,ln,gn)) {
      if (gn->type == END_GN) {
        struct graph_node *curr = copy_node(start);
        curr->arg = XSTRDUP(MTYPE_CMD_TOKENS, vector_slot(vline, n));
        // initialize a new argument list
        argv = list_new();
        argv->del = &free_nodelist;
        // push the currnode
        listnode_add(argv, curr);
        // push the endnode
        listnode_add(argv, copy_node(gn));
        // clean up
        list_delete (next);
        return argv;
      }
    }
    // no END_GN found, free resources and return null
    list_delete (next);
    return NULL;
  }

  // otherwise recurse on all nexthops
  struct list *bestmatch = NULL;
  for (ALL_LIST_ELEMENTS_RO(next,ln,gn))
    {
      if (gn->type == END_GN) // skip END_GN since we aren't at end of input
        continue;

      // get the result of the next node
      struct list *result = match_command_r (gn, vline, n+1);

      // compare to our current best match, and save if it's better
      if (result) {
        if (bestmatch) {
          int currprec = score_precedence (listgetdata(listhead(bestmatch)));
          int rsltprec = score_precedence (gn);
          if (currprec < rsltprec)
            list_delete (result);
          if (currprec > rsltprec) {
            list_delete (bestmatch);
            bestmatch = result;
          }
          if (currprec == rsltprec) {
            list_delete (bestmatch);
            list_delete (result);
            list_delete (next);
            return NULL;
          }
        }
        else
          bestmatch = result;
      }
    }

  if (bestmatch) {
    argv = list_new();
    listnode_add(argv, start);
    list_add_list(argv, bestmatch);
    list_free (bestmatch);
    list_delete (next);
    start->arg = XSTRDUP(MTYPE_CMD_TOKENS, vector_slot(vline, n));
    return argv;
  }
  else
    return NULL;
}

struct list *
match_command_complete (struct graph_node *start, const char *line, enum filter_type filter)
{
  enum match_type minmatch = filter + 1;

  // vectorize command line
  vector vline = cmd_make_strvec (line);

  // pointer to next input token to match
  char *token;

  struct list *current  = list_new(), // current nodes to match input token against
              *matched  = list_new(), // current nodes that match the input token
              *next     = list_new(); // possible next hops to current input token

  // pointers used for iterating lists
  struct graph_node *gn;
  struct listnode *node;

  // add all children of start node to list
  add_nexthops(next, start);

  unsigned int idx;
  for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++)
  {
    list_free (current);
    current = next;
    next = list_new();

    token = vector_slot(vline, idx);

    list_delete_all_node(matched);

    for (ALL_LIST_ELEMENTS_RO(current,node,gn))
    {
      if (match_token(gn, token, filter) >= minmatch) {
        listnode_add(matched, gn);
        add_nexthops(next, gn);
      }
    }
  }

  /* Variable summary
   * -----------------------------------------------------------------
   * token    = last input token processed
   * idx      = index in `command` of last token processed
   * current  = set of all transitions from the previous input token
   * matched  = set of all nodes reachable with current input
   * next     = set of all nodes reachable from all nodes in `matched`
   */
  list_free (current);
  list_free (matched);

  cmd_free_strvec(vline);

  return next;
}

/**
 * Adds all children that are reachable by one parser hop
 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
 * nodes are treated as transparent.
 *
 * @param[out] l the list to add the children to
 * @param[in] node the node to get the children of
 * @return the number of children added to the list
 */
static int
add_nexthops(struct list *l, struct graph_node *node)
{
  int added = 0;
  struct graph_node *child;
  for (unsigned int i = 0; i < vector_active(node->children); i++)
  {
    child = vector_slot(node->children, i);
    switch (child->type) {
      case OPTION_GN:
      case SELECTOR_GN:
      case NUL_GN:
        added += add_nexthops(l, child);
        break;
      default:
        listnode_add(l, child);
        added++;
    }
  }
  return added;
}


/* matching utility functions */

/**
 * Determines the minimum acceptable matching level
 * for a given node type that can be accepted as a
 * full match. Used for things like abbreviating
 * commands, e.g. `conf t`.
 */
static enum match_type
min_match_level(enum node_type type)
{
  switch (type) {
    case WORD_GN:
      return partly_match;
    default:
      return exact_match;
  }
}

static int
score_precedence (struct graph_node *node)
{
  switch (node->type)
  {
    // these should be mutually exclusive,
    // or never compared
    case IPV4_GN:
    case IPV4_PREFIX_GN:
    case IPV6_GN:
    case IPV6_PREFIX_GN:
    case RANGE_GN:
    case NUMBER_GN:
      return 1;
    case WORD_GN:
      return 2;
    case VARIABLE_GN:
      return 3;
    default:
      return 10;
  }
}

static enum match_type
match_token (struct graph_node *node, char *token, enum filter_type filter)
{
  switch (node->type) {
    case WORD_GN:
      return match_word (node, token, filter);
    case IPV4_GN:
      return match_ipv4 (token);
    case IPV4_PREFIX_GN:
      return match_ipv4_prefix (token);
    case IPV6_GN:
      return match_ipv6 (token);
    case IPV6_PREFIX_GN:
      return match_ipv6_prefix (token);
    case RANGE_GN:
      return match_range (node, token);
    case NUMBER_GN:
      return match_number (node, token);
    case VARIABLE_GN:
      return match_variable (node, token);
    case END_GN:
    default:
      return no_match;
  }
}

#define IPV4_ADDR_STR   "0123456789."
#define IPV4_PREFIX_STR "0123456789./"

static enum match_type
match_ipv4 (const char *str)
{
  const char *sp;
  int dots = 0, nums = 0;
  char buf[4];

  if (str == NULL)
    return partly_match;

  for (;;)
    {
      memset (buf, 0, sizeof (buf));
      sp = str;
      while (*str != '\0')
        {
          if (*str == '.')
            {
              if (dots >= 3)
                return no_match;

              if (*(str + 1) == '.')
                return no_match;

              if (*(str + 1) == '\0')
                return partly_match;

              dots++;
              break;
            }
          if (!isdigit ((int) *str))
            return no_match;

          str++;
        }

      if (str - sp > 3)
        return no_match;

      strncpy (buf, sp, str - sp);
      if (atoi (buf) > 255)
        return no_match;

      nums++;

      if (*str == '\0')
        break;

      str++;
    }

  if (nums < 4)
    return partly_match;

  return exact_match;
}

static enum match_type
match_ipv4_prefix (const char *str)
{
  const char *sp;
  int dots = 0;
  char buf[4];

  if (str == NULL)
    return partly_match;

  for (;;)
    {
      memset (buf, 0, sizeof (buf));
      sp = str;
      while (*str != '\0' && *str != '/')
        {
          if (*str == '.')
            {
              if (dots == 3)
                return no_match;

              if (*(str + 1) == '.' || *(str + 1) == '/')
                return no_match;

              if (*(str + 1) == '\0')
                return partly_match;

              dots++;
              break;
            }

          if (!isdigit ((int) *str))
            return no_match;

          str++;
        }

      if (str - sp > 3)
        return no_match;

      strncpy (buf, sp, str - sp);
      if (atoi (buf) > 255)
        return no_match;

      if (dots == 3)
        {
          if (*str == '/')
            {
              if (*(str + 1) == '\0')
                return partly_match;

              str++;
              break;
            }
          else if (*str == '\0')
            return partly_match;
        }

      if (*str == '\0')
        return partly_match;

      str++;
    }

  sp = str;
  while (*str != '\0')
    {
      if (!isdigit ((int) *str))
        return no_match;

      str++;
    }

  if (atoi (sp) > 32)
    return no_match;

  return exact_match;
}

#ifdef HAVE_IPV6
#define IPV6_ADDR_STR   "0123456789abcdefABCDEF:."
#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"

static enum match_type
match_ipv6 (const char *str)
{
  struct sockaddr_in6 sin6_dummy;
  int ret;

  if (str == NULL)
    return partly_match;

  if (strspn (str, IPV6_ADDR_STR) != strlen (str))
    return no_match;

  ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);

  if (ret == 1)
    return exact_match;

  return no_match;
}

static enum match_type
match_ipv6_prefix (const char *str)
{
  struct sockaddr_in6 sin6_dummy;
  const char *delim = "/\0";
  char *dupe, *prefix, *mask, *context, *endptr;
  int nmask = -1;

  if (str == NULL)
    return partly_match;

  if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
    return no_match;

  /* tokenize to address + mask */
  dupe = XMALLOC(MTYPE_TMP, strlen(str)+1);
  strncpy(dupe, str, strlen(str)+1);
  prefix = strtok_r(dupe, delim, &context);
  mask   = strtok_r(NULL, delim, &context);

  if (!mask)
    return partly_match;

  /* validate prefix */
  if (inet_pton(AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
    return no_match;

  /* validate mask */
  nmask = strtol (mask, &endptr, 10);
  if (*endptr != '\0' || nmask < 0 || nmask > 128)
    return no_match;

  XFREE(MTYPE_TMP, dupe);

  return exact_match;
}
#endif

static enum match_type
match_range (struct graph_node *rangenode, const char *str)
{
  char *endptr = NULL;
  signed long val;

  if (str == NULL)
    return 1;

  val = strtoll (str, &endptr, 10);
  if (*endptr != '\0')
    return 0;
  val = llabs(val);

  if (val < rangenode->min || val > rangenode->max)
    return no_match;
  else
    return exact_match;
}

static enum match_type
match_word(struct graph_node *wordnode,
           const char *word,
           enum filter_type filter)
{
  if (filter == FILTER_RELAXED)
  {
    if (!word || !strlen(word))
      return partly_match;
    else if (!strncmp(wordnode->text, word, strlen(word)))
      return !strcmp(wordnode->text, word) ? exact_match : partly_match;
    else
      return no_match;
  }
  else
  {
     if (!word)
       return no_match;
     else
       return !strcmp(wordnode->text, word) ? exact_match : no_match;
  }
}

static enum match_type
match_number(struct graph_node *numnode, const char *word)
{
  if (!strcmp("\0", word)) return no_match;
  char *endptr;
  long num = strtol(word, &endptr, 10);
  if (endptr != '\0') return no_match;
  return num == numnode->value ? exact_match : no_match;
}

#define VARIABLE_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"

static enum match_type
match_variable(struct graph_node *varnode, const char *word)
{
  return strlen(word) == strspn(word, VARIABLE_ALPHABET) && isalpha(word[0]) ?
     exact_match : no_match;
}