5972b80808053c7c3aec1c524442201d14e7935e
[dpdk.git] / app / test-pmd / cmdline_flow.c
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright 2016 6WIND S.A.
5  *   Copyright 2016 Mellanox.
6  *
7  *   Redistribution and use in source and binary forms, with or without
8  *   modification, are permitted provided that the following conditions
9  *   are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *     * Neither the name of 6WIND S.A. nor the names of its
18  *       contributors may be used to endorse or promote products derived
19  *       from this software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include <stddef.h>
35 #include <stdint.h>
36 #include <stdio.h>
37 #include <inttypes.h>
38 #include <errno.h>
39 #include <ctype.h>
40 #include <string.h>
41
42 #include <rte_common.h>
43 #include <rte_ethdev.h>
44 #include <rte_byteorder.h>
45 #include <cmdline_parse.h>
46 #include <rte_flow.h>
47
48 #include "testpmd.h"
49
50 /** Parser token indices. */
51 enum index {
52         /* Special tokens. */
53         ZERO = 0,
54         END,
55
56         /* Common tokens. */
57         INTEGER,
58         UNSIGNED,
59         PORT_ID,
60         GROUP_ID,
61
62         /* Top-level command. */
63         FLOW,
64
65         /* Sub-level commands. */
66         FLUSH,
67         LIST,
68
69         /* List arguments. */
70         LIST_GROUP,
71 };
72
73 /** Maximum number of subsequent tokens and arguments on the stack. */
74 #define CTX_STACK_SIZE 16
75
76 /** Parser context. */
77 struct context {
78         /** Stack of subsequent token lists to process. */
79         const enum index *next[CTX_STACK_SIZE];
80         /** Arguments for stacked tokens. */
81         const void *args[CTX_STACK_SIZE];
82         enum index curr; /**< Current token index. */
83         enum index prev; /**< Index of the last token seen. */
84         int next_num; /**< Number of entries in next[]. */
85         int args_num; /**< Number of entries in args[]. */
86         uint32_t reparse:1; /**< Start over from the beginning. */
87         uint32_t eol:1; /**< EOL has been detected. */
88         uint32_t last:1; /**< No more arguments. */
89         uint16_t port; /**< Current port ID (for completions). */
90         void *object; /**< Address of current object for relative offsets. */
91 };
92
93 /** Token argument. */
94 struct arg {
95         uint32_t hton:1; /**< Use network byte ordering. */
96         uint32_t sign:1; /**< Value is signed. */
97         uint32_t offset; /**< Relative offset from ctx->object. */
98         uint32_t size; /**< Field size. */
99 };
100
101 /** Parser token definition. */
102 struct token {
103         /** Type displayed during completion (defaults to "TOKEN"). */
104         const char *type;
105         /** Help displayed during completion (defaults to token name). */
106         const char *help;
107         /**
108          * Lists of subsequent tokens to push on the stack. Each call to the
109          * parser consumes the last entry of that stack.
110          */
111         const enum index *const *next;
112         /** Arguments stack for subsequent tokens that need them. */
113         const struct arg *const *args;
114         /**
115          * Token-processing callback, returns -1 in case of error, the
116          * length of the matched string otherwise. If NULL, attempts to
117          * match the token name.
118          *
119          * If buf is not NULL, the result should be stored in it according
120          * to context. An error is returned if not large enough.
121          */
122         int (*call)(struct context *ctx, const struct token *token,
123                     const char *str, unsigned int len,
124                     void *buf, unsigned int size);
125         /**
126          * Callback that provides possible values for this token, used for
127          * completion. Returns -1 in case of error, the number of possible
128          * values otherwise. If NULL, the token name is used.
129          *
130          * If buf is not NULL, entry index ent is written to buf and the
131          * full length of the entry is returned (same behavior as
132          * snprintf()).
133          */
134         int (*comp)(struct context *ctx, const struct token *token,
135                     unsigned int ent, char *buf, unsigned int size);
136         /** Mandatory token name, no default value. */
137         const char *name;
138 };
139
140 /** Static initializer for the next field. */
141 #define NEXT(...) (const enum index *const []){ __VA_ARGS__, NULL, }
142
143 /** Static initializer for a NEXT() entry. */
144 #define NEXT_ENTRY(...) (const enum index []){ __VA_ARGS__, ZERO, }
145
146 /** Static initializer for the args field. */
147 #define ARGS(...) (const struct arg *const []){ __VA_ARGS__, NULL, }
148
149 /** Static initializer for ARGS() to target a field. */
150 #define ARGS_ENTRY(s, f) \
151         (&(const struct arg){ \
152                 .offset = offsetof(s, f), \
153                 .size = sizeof(((s *)0)->f), \
154         })
155
156 /** Static initializer for ARGS() to target a pointer. */
157 #define ARGS_ENTRY_PTR(s, f) \
158         (&(const struct arg){ \
159                 .size = sizeof(*((s *)0)->f), \
160         })
161
162 /** Parser output buffer layout expected by cmd_flow_parsed(). */
163 struct buffer {
164         enum index command; /**< Flow command. */
165         uint16_t port; /**< Affected port ID. */
166         union {
167                 struct {
168                         uint32_t *group;
169                         uint32_t group_n;
170                 } list; /**< List arguments. */
171         } args; /**< Command arguments. */
172 };
173
174 static const enum index next_list_attr[] = {
175         LIST_GROUP,
176         END,
177         ZERO,
178 };
179
180 static int parse_init(struct context *, const struct token *,
181                       const char *, unsigned int,
182                       void *, unsigned int);
183 static int parse_flush(struct context *, const struct token *,
184                        const char *, unsigned int,
185                        void *, unsigned int);
186 static int parse_list(struct context *, const struct token *,
187                       const char *, unsigned int,
188                       void *, unsigned int);
189 static int parse_int(struct context *, const struct token *,
190                      const char *, unsigned int,
191                      void *, unsigned int);
192 static int parse_port(struct context *, const struct token *,
193                       const char *, unsigned int,
194                       void *, unsigned int);
195 static int comp_none(struct context *, const struct token *,
196                      unsigned int, char *, unsigned int);
197 static int comp_port(struct context *, const struct token *,
198                      unsigned int, char *, unsigned int);
199
200 /** Token definitions. */
201 static const struct token token_list[] = {
202         /* Special tokens. */
203         [ZERO] = {
204                 .name = "ZERO",
205                 .help = "null entry, abused as the entry point",
206                 .next = NEXT(NEXT_ENTRY(FLOW)),
207         },
208         [END] = {
209                 .name = "",
210                 .type = "RETURN",
211                 .help = "command may end here",
212         },
213         /* Common tokens. */
214         [INTEGER] = {
215                 .name = "{int}",
216                 .type = "INTEGER",
217                 .help = "integer value",
218                 .call = parse_int,
219                 .comp = comp_none,
220         },
221         [UNSIGNED] = {
222                 .name = "{unsigned}",
223                 .type = "UNSIGNED",
224                 .help = "unsigned integer value",
225                 .call = parse_int,
226                 .comp = comp_none,
227         },
228         [PORT_ID] = {
229                 .name = "{port_id}",
230                 .type = "PORT ID",
231                 .help = "port identifier",
232                 .call = parse_port,
233                 .comp = comp_port,
234         },
235         [GROUP_ID] = {
236                 .name = "{group_id}",
237                 .type = "GROUP ID",
238                 .help = "group identifier",
239                 .call = parse_int,
240                 .comp = comp_none,
241         },
242         /* Top-level command. */
243         [FLOW] = {
244                 .name = "flow",
245                 .type = "{command} {port_id} [{arg} [...]]",
246                 .help = "manage ingress/egress flow rules",
247                 .next = NEXT(NEXT_ENTRY
248                              (FLUSH,
249                               LIST)),
250                 .call = parse_init,
251         },
252         /* Sub-level commands. */
253         [FLUSH] = {
254                 .name = "flush",
255                 .help = "destroy all flow rules",
256                 .next = NEXT(NEXT_ENTRY(PORT_ID)),
257                 .args = ARGS(ARGS_ENTRY(struct buffer, port)),
258                 .call = parse_flush,
259         },
260         [LIST] = {
261                 .name = "list",
262                 .help = "list existing flow rules",
263                 .next = NEXT(next_list_attr, NEXT_ENTRY(PORT_ID)),
264                 .args = ARGS(ARGS_ENTRY(struct buffer, port)),
265                 .call = parse_list,
266         },
267         /* List arguments. */
268         [LIST_GROUP] = {
269                 .name = "group",
270                 .help = "specify a group",
271                 .next = NEXT(next_list_attr, NEXT_ENTRY(GROUP_ID)),
272                 .args = ARGS(ARGS_ENTRY_PTR(struct buffer, args.list.group)),
273                 .call = parse_list,
274         },
275 };
276
277 /** Remove and return last entry from argument stack. */
278 static const struct arg *
279 pop_args(struct context *ctx)
280 {
281         return ctx->args_num ? ctx->args[--ctx->args_num] : NULL;
282 }
283
284 /** Add entry on top of the argument stack. */
285 static int
286 push_args(struct context *ctx, const struct arg *arg)
287 {
288         if (ctx->args_num == CTX_STACK_SIZE)
289                 return -1;
290         ctx->args[ctx->args_num++] = arg;
291         return 0;
292 }
293
294 /** Default parsing function for token name matching. */
295 static int
296 parse_default(struct context *ctx, const struct token *token,
297               const char *str, unsigned int len,
298               void *buf, unsigned int size)
299 {
300         (void)ctx;
301         (void)buf;
302         (void)size;
303         if (strncmp(str, token->name, len))
304                 return -1;
305         return len;
306 }
307
308 /** Parse flow command, initialize output buffer for subsequent tokens. */
309 static int
310 parse_init(struct context *ctx, const struct token *token,
311            const char *str, unsigned int len,
312            void *buf, unsigned int size)
313 {
314         struct buffer *out = buf;
315
316         /* Token name must match. */
317         if (parse_default(ctx, token, str, len, NULL, 0) < 0)
318                 return -1;
319         /* Nothing else to do if there is no buffer. */
320         if (!out)
321                 return len;
322         /* Make sure buffer is large enough. */
323         if (size < sizeof(*out))
324                 return -1;
325         /* Initialize buffer. */
326         memset(out, 0x00, sizeof(*out));
327         memset((uint8_t *)out + sizeof(*out), 0x22, size - sizeof(*out));
328         ctx->object = out;
329         return len;
330 }
331
332 /** Parse tokens for flush command. */
333 static int
334 parse_flush(struct context *ctx, const struct token *token,
335             const char *str, unsigned int len,
336             void *buf, unsigned int size)
337 {
338         struct buffer *out = buf;
339
340         /* Token name must match. */
341         if (parse_default(ctx, token, str, len, NULL, 0) < 0)
342                 return -1;
343         /* Nothing else to do if there is no buffer. */
344         if (!out)
345                 return len;
346         if (!out->command) {
347                 if (ctx->curr != FLUSH)
348                         return -1;
349                 if (sizeof(*out) > size)
350                         return -1;
351                 out->command = ctx->curr;
352                 ctx->object = out;
353         }
354         return len;
355 }
356
357 /** Parse tokens for list command. */
358 static int
359 parse_list(struct context *ctx, const struct token *token,
360            const char *str, unsigned int len,
361            void *buf, unsigned int size)
362 {
363         struct buffer *out = buf;
364
365         /* Token name must match. */
366         if (parse_default(ctx, token, str, len, NULL, 0) < 0)
367                 return -1;
368         /* Nothing else to do if there is no buffer. */
369         if (!out)
370                 return len;
371         if (!out->command) {
372                 if (ctx->curr != LIST)
373                         return -1;
374                 if (sizeof(*out) > size)
375                         return -1;
376                 out->command = ctx->curr;
377                 ctx->object = out;
378                 out->args.list.group =
379                         (void *)RTE_ALIGN_CEIL((uintptr_t)(out + 1),
380                                                sizeof(double));
381                 return len;
382         }
383         if (((uint8_t *)(out->args.list.group + out->args.list.group_n) +
384              sizeof(*out->args.list.group)) > (uint8_t *)out + size)
385                 return -1;
386         ctx->object = out->args.list.group + out->args.list.group_n++;
387         return len;
388 }
389
390 /**
391  * Parse signed/unsigned integers 8 to 64-bit long.
392  *
393  * Last argument (ctx->args) is retrieved to determine integer type and
394  * storage location.
395  */
396 static int
397 parse_int(struct context *ctx, const struct token *token,
398           const char *str, unsigned int len,
399           void *buf, unsigned int size)
400 {
401         const struct arg *arg = pop_args(ctx);
402         uintmax_t u;
403         char *end;
404
405         (void)token;
406         /* Argument is expected. */
407         if (!arg)
408                 return -1;
409         errno = 0;
410         u = arg->sign ?
411                 (uintmax_t)strtoimax(str, &end, 0) :
412                 strtoumax(str, &end, 0);
413         if (errno || (size_t)(end - str) != len)
414                 goto error;
415         if (!ctx->object)
416                 return len;
417         buf = (uint8_t *)ctx->object + arg->offset;
418         size = arg->size;
419         switch (size) {
420         case sizeof(uint8_t):
421                 *(uint8_t *)buf = u;
422                 break;
423         case sizeof(uint16_t):
424                 *(uint16_t *)buf = arg->hton ? rte_cpu_to_be_16(u) : u;
425                 break;
426         case sizeof(uint32_t):
427                 *(uint32_t *)buf = arg->hton ? rte_cpu_to_be_32(u) : u;
428                 break;
429         case sizeof(uint64_t):
430                 *(uint64_t *)buf = arg->hton ? rte_cpu_to_be_64(u) : u;
431                 break;
432         default:
433                 goto error;
434         }
435         return len;
436 error:
437         push_args(ctx, arg);
438         return -1;
439 }
440
441 /** Parse port and update context. */
442 static int
443 parse_port(struct context *ctx, const struct token *token,
444            const char *str, unsigned int len,
445            void *buf, unsigned int size)
446 {
447         struct buffer *out = &(struct buffer){ .port = 0 };
448         int ret;
449
450         if (buf)
451                 out = buf;
452         else {
453                 ctx->object = out;
454                 size = sizeof(*out);
455         }
456         ret = parse_int(ctx, token, str, len, out, size);
457         if (ret >= 0)
458                 ctx->port = out->port;
459         if (!buf)
460                 ctx->object = NULL;
461         return ret;
462 }
463
464 /** No completion. */
465 static int
466 comp_none(struct context *ctx, const struct token *token,
467           unsigned int ent, char *buf, unsigned int size)
468 {
469         (void)ctx;
470         (void)token;
471         (void)ent;
472         (void)buf;
473         (void)size;
474         return 0;
475 }
476
477 /** Complete available ports. */
478 static int
479 comp_port(struct context *ctx, const struct token *token,
480           unsigned int ent, char *buf, unsigned int size)
481 {
482         unsigned int i = 0;
483         portid_t p;
484
485         (void)ctx;
486         (void)token;
487         FOREACH_PORT(p, ports) {
488                 if (buf && i == ent)
489                         return snprintf(buf, size, "%u", p);
490                 ++i;
491         }
492         if (buf)
493                 return -1;
494         return i;
495 }
496
497 /** Internal context. */
498 static struct context cmd_flow_context;
499
500 /** Global parser instance (cmdline API). */
501 cmdline_parse_inst_t cmd_flow;
502
503 /** Initialize context. */
504 static void
505 cmd_flow_context_init(struct context *ctx)
506 {
507         /* A full memset() is not necessary. */
508         ctx->curr = ZERO;
509         ctx->prev = ZERO;
510         ctx->next_num = 0;
511         ctx->args_num = 0;
512         ctx->reparse = 0;
513         ctx->eol = 0;
514         ctx->last = 0;
515         ctx->port = 0;
516         ctx->object = NULL;
517 }
518
519 /** Parse a token (cmdline API). */
520 static int
521 cmd_flow_parse(cmdline_parse_token_hdr_t *hdr, const char *src, void *result,
522                unsigned int size)
523 {
524         struct context *ctx = &cmd_flow_context;
525         const struct token *token;
526         const enum index *list;
527         int len;
528         int i;
529
530         (void)hdr;
531         /* Restart as requested. */
532         if (ctx->reparse)
533                 cmd_flow_context_init(ctx);
534         token = &token_list[ctx->curr];
535         /* Check argument length. */
536         ctx->eol = 0;
537         ctx->last = 1;
538         for (len = 0; src[len]; ++len)
539                 if (src[len] == '#' || isspace(src[len]))
540                         break;
541         if (!len)
542                 return -1;
543         /* Last argument and EOL detection. */
544         for (i = len; src[i]; ++i)
545                 if (src[i] == '#' || src[i] == '\r' || src[i] == '\n')
546                         break;
547                 else if (!isspace(src[i])) {
548                         ctx->last = 0;
549                         break;
550                 }
551         for (; src[i]; ++i)
552                 if (src[i] == '\r' || src[i] == '\n') {
553                         ctx->eol = 1;
554                         break;
555                 }
556         /* Initialize context if necessary. */
557         if (!ctx->next_num) {
558                 if (!token->next)
559                         return 0;
560                 ctx->next[ctx->next_num++] = token->next[0];
561         }
562         /* Process argument through candidates. */
563         ctx->prev = ctx->curr;
564         list = ctx->next[ctx->next_num - 1];
565         for (i = 0; list[i]; ++i) {
566                 const struct token *next = &token_list[list[i]];
567                 int tmp;
568
569                 ctx->curr = list[i];
570                 if (next->call)
571                         tmp = next->call(ctx, next, src, len, result, size);
572                 else
573                         tmp = parse_default(ctx, next, src, len, result, size);
574                 if (tmp == -1 || tmp != len)
575                         continue;
576                 token = next;
577                 break;
578         }
579         if (!list[i])
580                 return -1;
581         --ctx->next_num;
582         /* Push subsequent tokens if any. */
583         if (token->next)
584                 for (i = 0; token->next[i]; ++i) {
585                         if (ctx->next_num == RTE_DIM(ctx->next))
586                                 return -1;
587                         ctx->next[ctx->next_num++] = token->next[i];
588                 }
589         /* Push arguments if any. */
590         if (token->args)
591                 for (i = 0; token->args[i]; ++i) {
592                         if (ctx->args_num == RTE_DIM(ctx->args))
593                                 return -1;
594                         ctx->args[ctx->args_num++] = token->args[i];
595                 }
596         return len;
597 }
598
599 /** Return number of completion entries (cmdline API). */
600 static int
601 cmd_flow_complete_get_nb(cmdline_parse_token_hdr_t *hdr)
602 {
603         struct context *ctx = &cmd_flow_context;
604         const struct token *token = &token_list[ctx->curr];
605         const enum index *list;
606         int i;
607
608         (void)hdr;
609         /* Tell cmd_flow_parse() that context must be reinitialized. */
610         ctx->reparse = 1;
611         /* Count number of tokens in current list. */
612         if (ctx->next_num)
613                 list = ctx->next[ctx->next_num - 1];
614         else
615                 list = token->next[0];
616         for (i = 0; list[i]; ++i)
617                 ;
618         if (!i)
619                 return 0;
620         /*
621          * If there is a single token, use its completion callback, otherwise
622          * return the number of entries.
623          */
624         token = &token_list[list[0]];
625         if (i == 1 && token->comp) {
626                 /* Save index for cmd_flow_get_help(). */
627                 ctx->prev = list[0];
628                 return token->comp(ctx, token, 0, NULL, 0);
629         }
630         return i;
631 }
632
633 /** Return a completion entry (cmdline API). */
634 static int
635 cmd_flow_complete_get_elt(cmdline_parse_token_hdr_t *hdr, int index,
636                           char *dst, unsigned int size)
637 {
638         struct context *ctx = &cmd_flow_context;
639         const struct token *token = &token_list[ctx->curr];
640         const enum index *list;
641         int i;
642
643         (void)hdr;
644         /* Tell cmd_flow_parse() that context must be reinitialized. */
645         ctx->reparse = 1;
646         /* Count number of tokens in current list. */
647         if (ctx->next_num)
648                 list = ctx->next[ctx->next_num - 1];
649         else
650                 list = token->next[0];
651         for (i = 0; list[i]; ++i)
652                 ;
653         if (!i)
654                 return -1;
655         /* If there is a single token, use its completion callback. */
656         token = &token_list[list[0]];
657         if (i == 1 && token->comp) {
658                 /* Save index for cmd_flow_get_help(). */
659                 ctx->prev = list[0];
660                 return token->comp(ctx, token, index, dst, size) < 0 ? -1 : 0;
661         }
662         /* Otherwise make sure the index is valid and use defaults. */
663         if (index >= i)
664                 return -1;
665         token = &token_list[list[index]];
666         snprintf(dst, size, "%s", token->name);
667         /* Save index for cmd_flow_get_help(). */
668         ctx->prev = list[index];
669         return 0;
670 }
671
672 /** Populate help strings for current token (cmdline API). */
673 static int
674 cmd_flow_get_help(cmdline_parse_token_hdr_t *hdr, char *dst, unsigned int size)
675 {
676         struct context *ctx = &cmd_flow_context;
677         const struct token *token = &token_list[ctx->prev];
678
679         (void)hdr;
680         /* Tell cmd_flow_parse() that context must be reinitialized. */
681         ctx->reparse = 1;
682         if (!size)
683                 return -1;
684         /* Set token type and update global help with details. */
685         snprintf(dst, size, "%s", (token->type ? token->type : "TOKEN"));
686         if (token->help)
687                 cmd_flow.help_str = token->help;
688         else
689                 cmd_flow.help_str = token->name;
690         return 0;
691 }
692
693 /** Token definition template (cmdline API). */
694 static struct cmdline_token_hdr cmd_flow_token_hdr = {
695         .ops = &(struct cmdline_token_ops){
696                 .parse = cmd_flow_parse,
697                 .complete_get_nb = cmd_flow_complete_get_nb,
698                 .complete_get_elt = cmd_flow_complete_get_elt,
699                 .get_help = cmd_flow_get_help,
700         },
701         .offset = 0,
702 };
703
704 /** Populate the next dynamic token. */
705 static void
706 cmd_flow_tok(cmdline_parse_token_hdr_t **hdr,
707              cmdline_parse_token_hdr_t *(*hdrs)[])
708 {
709         struct context *ctx = &cmd_flow_context;
710
711         /* Always reinitialize context before requesting the first token. */
712         if (!(hdr - *hdrs))
713                 cmd_flow_context_init(ctx);
714         /* Return NULL when no more tokens are expected. */
715         if (!ctx->next_num && ctx->curr) {
716                 *hdr = NULL;
717                 return;
718         }
719         /* Determine if command should end here. */
720         if (ctx->eol && ctx->last && ctx->next_num) {
721                 const enum index *list = ctx->next[ctx->next_num - 1];
722                 int i;
723
724                 for (i = 0; list[i]; ++i) {
725                         if (list[i] != END)
726                                 continue;
727                         *hdr = NULL;
728                         return;
729                 }
730         }
731         *hdr = &cmd_flow_token_hdr;
732 }
733
734 /** Dispatch parsed buffer to function calls. */
735 static void
736 cmd_flow_parsed(const struct buffer *in)
737 {
738         switch (in->command) {
739         case FLUSH:
740                 port_flow_flush(in->port);
741                 break;
742         case LIST:
743                 port_flow_list(in->port, in->args.list.group_n,
744                                in->args.list.group);
745                 break;
746         default:
747                 break;
748         }
749 }
750
751 /** Token generator and output processing callback (cmdline API). */
752 static void
753 cmd_flow_cb(void *arg0, struct cmdline *cl, void *arg2)
754 {
755         if (cl == NULL)
756                 cmd_flow_tok(arg0, arg2);
757         else
758                 cmd_flow_parsed(arg0);
759 }
760
761 /** Global parser instance (cmdline API). */
762 cmdline_parse_inst_t cmd_flow = {
763         .f = cmd_flow_cb,
764         .data = NULL, /**< Unused. */
765         .help_str = NULL, /**< Updated by cmd_flow_get_help(). */
766         .tokens = {
767                 NULL,
768         }, /**< Tokens are returned by cmd_flow_tok(). */
769 };