vat2: do not require _crc field in API messages
[vpp.git] / src / vat2 / main.c
1 /*
2  * Copyright (c) 2020 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <stdbool.h>
19 #include <ctype.h>
20 #include <getopt.h>
21 #include <string.h>
22 #include <vlib/vlib.h>
23 #include <vlibapi/api_types.h>
24 #include <vppinfra/hash.h>
25 #include <vppinfra/cJSON.h>
26
27 /* VPP API client includes */
28 #include <vpp-api/client/vppapiclient.h>
29
30 #include <limits.h>
31 #include "vat2.h"
32
33 /*
34  * Filter these messages as they are used to manage the API connection to VPP
35  */
36 char *filter_messages_strings[] = { "memclnt_create",
37                                     "memclnt_delete",
38                                     "sockclnt_create",
39                                     "sockclnt_delete",
40                                     "memclnt_rx_thread_suspend",
41                                     "memclnt_read_timeout",
42                                     "rx_thread_exit",
43                                     "trace_plugin_msg_ids",
44                                     0 };
45
46 static bool
47 filter_message (char *msgname)
48 {
49   char **p = filter_messages_strings;
50
51   while (*p)
52     {
53       if (strcmp (*p, msgname) == 0)
54         return true;
55       p++;
56     }
57   return false;
58 }
59
60 uword *function_by_name;
61 bool debug = false;
62
63 char *vat2_plugin_path;
64 static void
65 vat2_find_plugin_path ()
66 {
67   char *p, path[PATH_MAX];
68   int rv;
69   u8 *s;
70
71   /* find executable path */
72   if ((rv = readlink ("/proc/self/exe", path, PATH_MAX - 1)) == -1)
73     return;
74
75   /* readlink doesn't provide null termination */
76   path[rv] = 0;
77
78   /* strip filename */
79   if ((p = strrchr (path, '/')) == 0)
80     return;
81   *p = 0;
82
83   /* strip bin/ */
84   if ((p = strrchr (path, '/')) == 0)
85     return;
86   *p = 0;
87
88   s = format (0, "%s/lib/" CLIB_TARGET_TRIPLET "/vat2_plugins:"
89               "%s/lib/vat2_plugins", path, path);
90   vec_add1 (s, 0);
91   vat2_plugin_path = (char *) s;
92 }
93
94 void
95 vac_callback (unsigned char *data, int len)
96 {
97   u16 result_msg_id = ntohs(*((u16 *)data));
98   DBG("Received something async: %d\n", result_msg_id);
99 }
100
101 int vat2_load_plugins (char *path, char *filter, int *loaded);
102
103 static int
104 register_function (void)
105 {
106   int loaded;
107
108   vat2_find_plugin_path();
109   DBG("Plugin Path %s\n", vat2_plugin_path);
110   int rv = vat2_load_plugins(vat2_plugin_path, 0, &loaded);
111   DBG("Loaded %u plugins\n", loaded);
112   return rv;
113 }
114
115 struct apifuncs_s
116 {
117   cJSON (*f) (cJSON *);
118   cJSON (*tojson) (void *);
119   u32 crc;
120 };
121
122 struct apifuncs_s *apifuncs = 0;
123
124 void
125 vat2_register_function (char *name, cJSON (*f) (cJSON *),
126                         cJSON (*tojson) (void *), u32 crc)
127 {
128   struct apifuncs_s funcs = { .f = f, .tojson = tojson, .crc = crc };
129   vec_add1 (apifuncs, funcs);
130   hash_set_mem (function_by_name, name, vec_len (apifuncs) - 1);
131 }
132
133 static int
134 vat2_exec_command_by_name (char *msgname, cJSON *o)
135 {
136   u32 crc = 0;
137   if (filter_message (msgname))
138     return 0;
139
140   cJSON *crc_obj = cJSON_GetObjectItem (o, "_crc");
141   if (crc_obj)
142     {
143       char *crc_str = cJSON_GetStringValue (crc_obj);
144       crc = (u32) strtol (crc_str, NULL, 16);
145     }
146
147   uword *p = hash_get_mem (function_by_name, msgname);
148   if (!p)
149     {
150       fprintf (stderr, "No such command %s\n", msgname);
151       return -1;
152     }
153   if (crc && crc != apifuncs[p[0]].crc)
154     {
155       fprintf (stderr, "API CRC does not match: %s!\n", msgname);
156     }
157
158   cJSON *(*fp) (cJSON *);
159   fp = (void *) apifuncs[p[0]].f;
160   cJSON *r = (*fp) (o);
161
162   if (r)
163     {
164       char *output = cJSON_Print (r);
165       cJSON_Delete (r);
166       printf ("%s\n", output);
167       free (output);
168     }
169   else
170     {
171       fprintf (stderr, "Call failed: %s\n", msgname);
172       return -1;
173     }
174   return 0;
175 }
176
177 static int
178 vat2_exec_command (cJSON *o)
179 {
180
181   cJSON *msg_id_obj = cJSON_GetObjectItem (o, "_msgname");
182   if (!msg_id_obj)
183     {
184       fprintf (stderr, "Missing '_msgname' element!\n");
185       return -1;
186     }
187
188   char *name = cJSON_GetStringValue (msg_id_obj);
189
190   return vat2_exec_command_by_name (name, o);
191 }
192
193 static void
194 print_template (char *msgname)
195 {
196   uword *p = hash_get_mem (function_by_name, msgname);
197   if (!p)
198     goto error;
199
200   cJSON *(*fp) (void *);
201   fp = (void *) apifuncs[p[0]].tojson;
202   if (!fp)
203     goto error;
204
205   void *scratch = malloc (2048);
206   if (!scratch)
207     goto error;
208
209   memset (scratch, 0, 2048);
210   cJSON *t = fp (scratch);
211   if (!t)
212     goto error;
213   free (scratch);
214   char *output = cJSON_Print (t);
215   if (!output)
216     goto error;
217
218   cJSON_Delete (t);
219   printf ("%s\n", output);
220   free (output);
221
222   return;
223
224 error:
225   fprintf (stderr, "error printing template for: %s\n", msgname);
226 }
227
228 static void
229 dump_apis (void)
230 {
231   char *name;
232   u32 *i;
233   hash_foreach_mem (name, i, function_by_name, ({ printf ("%s\n", name); }));
234 }
235
236 static void
237 print_help (void)
238 {
239   char *help_string =
240     "Usage: vat2 [OPTION] <message-name> <JSON object>\n"
241     "Send API message to VPP and print reply\n"
242     "\n"
243     "-d, --debug       Print additional information\n"
244     "-p, --prefix      Specify shared memory prefix to connect to a given VPP "
245     "instance\n"
246     "-f, --file        File containing a JSON object with the arguments for "
247     "the message to send\n"
248     "--dump-apis       List all APIs available in VAT2 (might not reflect "
249     "running VPP)\n"
250     "-t, --template    Print a template JSON object for given API message\n"
251     "\n";
252   printf ("%s", help_string);
253 }
254
255 int main (int argc, char **argv)
256 {
257   /* Create a heap of 64MB */
258   clib_mem_init (0, 64 << 20);
259   char *filename = 0, *prefix = 0;
260   int index;
261   int c;
262   opterr = 0;
263   cJSON *o = 0;
264   int option_index = 0;
265   bool dump_api = false;
266   bool template = false;
267   char *msgname = 0;
268   static int debug_flag;
269   static struct option long_options[] = {
270     { "debug", no_argument, &debug_flag, 1 },
271     { "prefix", optional_argument, 0, 'p' },
272     { "file", required_argument, 0, 'f' },
273     { "dump-apis", no_argument, 0, 0 },
274     { "template", no_argument, 0, 't' },
275     { 0, 0, 0, 0 }
276   };
277
278   while ((c = getopt_long (argc, argv, "hdp:f:", long_options,
279                            &option_index)) != -1)
280     {
281       switch (c)
282         {
283         case 0:
284           if (option_index == 3)
285             dump_api = true;
286           break;
287         case 'd':
288           debug = true;
289           break;
290         case 't':
291           template = true;
292           break;
293         case 'p':
294           prefix = optarg;
295           break;
296         case 'f':
297           filename = optarg;
298           break;
299         case '?':
300           print_help ();
301           return 1;
302         default:
303           abort ();
304         }
305     }
306   debug = debug_flag == 1 ? true : false;
307   DBG ("debug = %d, filename = %s shared memory prefix: %s\n", debug, filename,
308        prefix);
309
310   for (index = optind; index < argc; index++)
311     DBG ("Non-option argument %s\n", argv[index]);
312
313   index = optind;
314
315   if (argc > index + 2)
316     {
317       fprintf (stderr, "%s: Too many arguments\n", argv[0]);
318       exit (-1);
319     }
320
321   /* Load plugins */
322   function_by_name = hash_create_string (0, sizeof (uword));
323   int res = register_function();
324   if (res < 0) {
325     fprintf(stderr, "%s: loading plugins failed\n", argv[0]);
326     exit(-1);
327   }
328
329   if (template)
330     {
331       print_template (argv[index]);
332       exit (0);
333     }
334
335   if (dump_api)
336     {
337       dump_apis ();
338       exit (0);
339     }
340
341   /* Read message arguments from command line */
342   if (argc >= (index + 1))
343     {
344       msgname = argv[index];
345     }
346   if (argc == (index + 2)) {
347     o = cJSON_Parse(argv[index+1]);
348     if (!o) {
349       fprintf(stderr, "%s: Failed parsing JSON input: %s\n", argv[0], cJSON_GetErrorPtr());
350       exit(-1);
351     }
352   }
353
354   if (!msgname && !filename)
355     {
356       print_help ();
357       exit (-1);
358     }
359
360   /* Read message from file */
361   if (filename) {
362       if (argc > index)
363         {
364           fprintf (stderr, "%s: Superfluous arguments when filename given\n",
365                    argv[0]);
366           exit (-1);
367         }
368
369     FILE *f = fopen(filename, "r");
370     size_t chunksize, bufsize;
371     size_t n_read = 0;
372     size_t n;
373
374     if (!f) {
375       fprintf(stderr, "%s: can't open file: %s\n", argv[0], filename);
376       exit(-1);
377     }
378
379     chunksize = bufsize = 1024;
380     char *buf = malloc(bufsize);
381     while ((n = fread (buf + n_read, 1, chunksize, f)))
382       {
383         n_read += n;
384         if (n == chunksize)
385           {
386             bufsize += chunksize;
387             buf = realloc (buf, bufsize);
388           }
389       }
390     fclose(f);
391     if (n_read) {
392       o = cJSON_Parse(buf);
393       if (!o) {
394         fprintf(stderr, "%s: Failed parsing JSON input: %s\n", argv[0], cJSON_GetErrorPtr());
395         exit(-1);
396       }
397     }
398     free (buf);
399   }
400
401   if (!o)
402     {
403       fprintf (stderr, "%s: Failed parsing JSON input\n", argv[0]);
404       exit (-1);
405     }
406
407   if (vac_connect ("vat2", prefix, 0, 1024))
408     {
409       fprintf (stderr, "Failed connecting to VPP\n");
410       exit (-1);
411     }
412
413   if (msgname)
414     {
415       vat2_exec_command_by_name (msgname, o);
416     }
417   else
418     {
419       if (cJSON_IsArray (o))
420         {
421           size_t size = cJSON_GetArraySize (o);
422           for (int i = 0; i < size; i++)
423             vat2_exec_command (cJSON_GetArrayItem (o, i));
424         }
425     }
426   cJSON_Delete (o);
427   vac_disconnect();
428   exit (0);
429
430 }