api: provide api definition over api
[vpp.git] / src / tools / vppapigen / vppapigen_c.py
old mode 100644 (file)
new mode 100755 (executable)
index fdbb727..fb7de0a
@@ -123,7 +123,6 @@ class ToJSON:
                 )
             )
         else:
-
             write(
                 '    cJSON_AddStringToObject(o, "{n}", (char *)a->{n});\n'.format(
                     n=o.fieldname
@@ -751,6 +750,17 @@ TOP_BOILERPLATE = """\
 #endif
 
 #define VL_API_PACKED(x) x __attribute__ ((packed))
+
+/*
+ * Note: VL_API_MAX_ARRAY_SIZE is set to an arbitrarily large limit.
+ *
+ * However, any message with a ~2 billion element array is likely to break the
+ * api handling long before this limit causes array element endian issues.
+ *
+ * Applications should be written to create reasonable api messages.
+ */
+#define VL_API_MAX_ARRAY_SIZE 0x7fffffff
+
 """
 
 BOTTOM_BOILERPLATE = """\
@@ -1028,9 +1038,9 @@ def printfun(objs, stream, modulename):
 """
 
     signature = """\
-static inline void *vl_api_{name}_t_print{suffix} (vl_api_{name}_t *a, void *handle)
+static inline u8 *vl_api_{name}_t_format (u8 *s,  va_list *args)
 {{
-    u8 *s = 0;
+    __attribute__((unused)) vl_api_{name}_t *a = va_arg (*args, vl_api_{name}_t *);
     u32 indent __attribute__((unused)) = 2;
     int i __attribute__((unused));
 """
@@ -1041,27 +1051,14 @@ static inline void *vl_api_{name}_t_print{suffix} (vl_api_{name}_t *a, void *han
     pp = Printfun(stream)
     for t in objs:
         if t.manual_print:
-            write("/***** manual: vl_api_%s_t_print  *****/\n\n" % t.name)
+            write("/***** manual: vl_api_%s_t_format *****/\n\n" % t.name)
             continue
         write(signature.format(name=t.name, suffix=""))
         write("    /* Message definition: vl_api_{}_t: */\n".format(t.name))
         write('    s = format(s, "vl_api_%s_t:");\n' % t.name)
         for o in t.block:
             pp.print_obj(o, stream)
-        write("    vec_add1(s, 0);\n")
-        write("    vl_print (handle, (char *)s);\n")
-        write("    vec_free (s);\n")
-        write("    return handle;\n")
-        write("}\n\n")
-
-        write(signature.format(name=t.name, suffix="_json"))
-        write("    cJSON * o = vl_api_{}_t_tojson(a);\n".format(t.name))
-        write("    (void)s;\n")
-        write("    char *out = cJSON_Print(o);\n")
-        write("    vl_print(handle, out);\n")
-        write("    cJSON_Delete(o);\n")
-        write("    cJSON_free(out);\n")
-        write("    return handle;\n")
+        write("    return s;\n")
         write("}\n\n")
 
     write("\n#endif")
@@ -1103,7 +1100,7 @@ static inline u8 *format_vl_api_{name}_t (u8 *s, va_list * args)
             continue
 
         if t.manual_print:
-            write("/***** manual: vl_api_%s_t_print  *****/\n\n" % t.name)
+            write("/***** manual: vl_api_%s_t_format *****/\n\n" % t.name)
             continue
 
         if t.__class__.__name__ == "Using":
@@ -1146,9 +1143,21 @@ ENDIAN_STRINGS = {
 }
 
 
+def get_endian_string(o, type):
+    """Return proper endian string conversion function"""
+    try:
+        if o.to_network:
+            return ENDIAN_STRINGS[type].replace("net_to_host", "host_to_net")
+    except:
+        pass
+    return ENDIAN_STRINGS[type]
+
+
 def endianfun_array(o):
     """Generate endian functions for arrays"""
     forloop = """\
+    {comment}
+    ASSERT((u32){length} <= (u32)VL_API_MAX_ARRAY_SIZE);
     for (i = 0; i < {length}; i++) {{
         a->{name}[i] = {format}(a->{name}[i]);
     }}
@@ -1160,6 +1169,19 @@ def endianfun_array(o):
     }}
 """
 
+    to_network_comment = ""
+    try:
+        if o.to_network:
+            to_network_comment = """/*
+     * Array fields processed first to handle variable length arrays and size
+     * field endian conversion in the proper order for to-network messages.
+     * Message fields have been sorted by type in the code generator, thus fields
+     * in this generated code may be converted in a different order than specified
+     * in the *.api file.
+     */"""
+    except:
+        pass
+
     output = ""
     if o.fieldtype == "u8" or o.fieldtype == "string" or o.fieldtype == "bool":
         output += "    /* a->{n} = a->{n} (no-op) */\n".format(n=o.fieldname)
@@ -1167,7 +1189,10 @@ def endianfun_array(o):
         lfield = "a->" + o.lengthfield if o.lengthfield else o.length
         if o.fieldtype in ENDIAN_STRINGS:
             output += forloop.format(
-                length=lfield, format=ENDIAN_STRINGS[o.fieldtype], name=o.fieldname
+                comment=to_network_comment,
+                length=lfield,
+                format=get_endian_string(o, o.fieldtype),
+                name=o.fieldname,
             )
         else:
             output += forloop_format.format(
@@ -1194,7 +1219,7 @@ def endianfun_obj(o):
         return output
     if o.fieldtype in ENDIAN_STRINGS:
         output += "    a->{name} = {format}(a->{name});\n".format(
-            name=o.fieldname, format=ENDIAN_STRINGS[o.fieldtype]
+            name=o.fieldname, format=get_endian_string(o, o.fieldtype)
         )
     elif o.fieldtype.startswith("vl_api_"):
         output += "    {type}_endian(&a->{name});\n".format(
@@ -1216,10 +1241,13 @@ def endianfun(objs, modulename):
 #define included_{module}_endianfun
 
 #undef clib_net_to_host_uword
+#undef clib_host_to_net_uword
 #ifdef LP64
 #define clib_net_to_host_uword clib_net_to_host_u64
+#define clib_host_to_net_uword clib_host_to_net_u64
 #else
 #define clib_net_to_host_uword clib_net_to_host_u32
+#define clib_host_to_net_uword clib_host_to_net_u32
 #endif
 
 """
@@ -1232,10 +1260,17 @@ static inline void vl_api_{name}_t_endian (vl_api_{name}_t *a)
 """
 
     for t in objs:
+        # Outbound (to network) messages are identified by message nomenclature
+        # i.e. message names ending with these suffixes are 'to network'
+        if t.name.endswith("_reply") or t.name.endswith("_details"):
+            t.to_network = True
+        else:
+            t.to_network = False
+
         if t.__class__.__name__ == "Enum" or t.__class__.__name__ == "EnumFlag":
             output += signature.format(name=t.name)
             if t.enumtype in ENDIAN_STRINGS:
-                output += "    *a = {}(*a);\n".format(ENDIAN_STRINGS[t.enumtype])
+                output += "    *a = {}(*a);\n".format(get_endian_string(t, t.enumtype))
             else:
                 output += "    /* a->{name} = a->{name} (no-op) */\n".format(
                     name=t.name
@@ -1255,7 +1290,9 @@ static inline void vl_api_{name}_t_endian (vl_api_{name}_t *a)
                     name=t.name
                 )
             elif t.alias["type"] in FORMAT_STRINGS:
-                output += "    *a = {}(*a);\n".format(ENDIAN_STRINGS[t.alias["type"]])
+                output += "    *a = {}(*a);\n".format(
+                    get_endian_string(t, t.alias["type"])
+                )
             else:
                 output += "    /* Not Implemented yet {} */".format(t.name)
             output += "}\n\n"
@@ -1263,7 +1300,15 @@ static inline void vl_api_{name}_t_endian (vl_api_{name}_t *a)
 
         output += signature.format(name=t.name)
 
+        # For outbound (to network) messages:
+        #   some arrays have dynamic length -- iterate over
+        #   them before changing endianness for the length field
+        #   by making the Array types show up first
+        if t.to_network:
+            t.block.sort(key=lambda x: x.type)
+
         for o in t.block:
+            o.to_network = t.to_network
             output += endianfun_obj(o)
         output += "}\n\n"
 
@@ -1330,7 +1375,7 @@ static inline uword vl_api_{name}_t_calc_size (vl_api_{name}_t *a)
                             )
                         lf = m[0]
                         if lf.fieldtype in ENDIAN_STRINGS:
-                            output += f" + {ENDIAN_STRINGS[lf.fieldtype]}(a->{b.lengthfield}) * sizeof(a->{b.fieldname}[0])"
+                            output += f" + {get_endian_string(b, lf.fieldtype)}(a->{b.lengthfield}) * sizeof(a->{b.fieldname}[0])"
                         elif lf.fieldtype == "u8":
                             output += (
                                 f" + a->{b.lengthfield} * sizeof(a->{b.fieldname}[0])"
@@ -1525,22 +1570,24 @@ def generate_c_boilerplate(services, defines, counters, file_crc, module, stream
 #undef vl_calsizefun
 
 /* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
 #define vl_printfun
 #include "{module}.api.h"
 #undef vl_printfun
 
+#include "{module}.api_json.h"
 """
 
     write(hdr.format(module=module))
-    write("static u16\n")
-    write("setup_message_id_table (void) {\n")
-    write("   api_main_t *am = my_api_main;\n")
-    write("   vl_msg_api_msg_config_t c;\n")
-    write(
-        '   u16 msg_id_base = vl_msg_api_get_msg_ids ("{}_{crc:08x}", '
-        "VL_MSG_{m}_LAST);\n".format(module, crc=file_crc, m=module.upper())
-    )
+    if len(defines) > 0:
+        write("static u16\n")
+        write("setup_message_id_table (void) {\n")
+        write("   api_main_t *am = my_api_main;\n")
+        write("   vl_msg_api_msg_config_t c;\n")
+        write(
+            '   u16 msg_id_base = vl_msg_api_get_msg_ids ("{}_{crc:08x}", '
+            "VL_MSG_{m}_LAST);\n".format(module, crc=file_crc, m=module.upper())
+        )
+        write(f"   vec_add1(am->json_api_repr, (u8 *)json_api_repr_{module});\n")
 
     for d in defines:
         write(
@@ -1556,12 +1603,10 @@ def generate_c_boilerplate(services, defines, counters, file_crc, module, stream
             " {{.id = VL_API_{ID} + msg_id_base,\n"
             '   .name = "{n}",\n'
             "   .handler = vl_api_{n}_t_handler,\n"
-            "   .cleanup = vl_noop_handler,\n"
             "   .endian = vl_api_{n}_t_endian,\n"
-            "   .print = vl_api_{n}_t_print,\n"
+            "   .format_fn = vl_api_{n}_t_format,\n"
             "   .traced = 1,\n"
             "   .replay = 1,\n"
-            "   .print_json = vl_api_{n}_t_print_json,\n"
             "   .tojson = vl_api_{n}_t_tojson,\n"
             "   .fromjson = vl_api_{n}_t_fromjson,\n"
             "   .calc_size = vl_api_{n}_t_calc_size,\n"
@@ -1577,12 +1622,10 @@ def generate_c_boilerplate(services, defines, counters, file_crc, module, stream
                 "{{.id = VL_API_{ID} + msg_id_base,\n"
                 '  .name = "{n}",\n'
                 "  .handler = 0,\n"
-                "  .cleanup = vl_noop_handler,\n"
                 "  .endian = vl_api_{n}_t_endian,\n"
-                "  .print = vl_api_{n}_t_print,\n"
+                "  .format_fn = vl_api_{n}_t_format,\n"
                 "  .traced = 1,\n"
                 "  .replay = 1,\n"
-                "  .print_json = vl_api_{n}_t_print_json,\n"
                 "  .tojson = vl_api_{n}_t_tojson,\n"
                 "  .fromjson = vl_api_{n}_t_fromjson,\n"
                 "  .calc_size = vl_api_{n}_t_calc_size,\n"
@@ -1594,8 +1637,33 @@ def generate_c_boilerplate(services, defines, counters, file_crc, module, stream
         except KeyError:
             pass
 
-    write("   return msg_id_base;\n")
-    write("}\n")
+        try:
+            if s.stream:
+                d = define_hash[s.stream_message]
+                write(
+                    "   c = (vl_msg_api_msg_config_t) "
+                    "{{.id = VL_API_{ID} + msg_id_base,\n"
+                    '  .name = "{n}",\n'
+                    "  .handler = 0,\n"
+                    "  .endian = vl_api_{n}_t_endian,\n"
+                    "  .format_fn = vl_api_{n}_t_format,\n"
+                    "  .traced = 1,\n"
+                    "  .replay = 1,\n"
+                    "  .tojson = vl_api_{n}_t_tojson,\n"
+                    "  .fromjson = vl_api_{n}_t_fromjson,\n"
+                    "  .calc_size = vl_api_{n}_t_calc_size,\n"
+                    "  .is_autoendian = {auto}}};\n".format(
+                        n=s.stream_message,
+                        ID=s.stream_message.upper(),
+                        auto=d.autoendian,
+                    )
+                )
+                write("   vl_msg_api_config (&c);\n")
+        except KeyError:
+            pass
+    if len(defines) > 0:
+        write("   return msg_id_base;\n")
+        write("}\n")
 
     severity = {
         "error": "VL_COUNTER_SEVERITY_ERROR",
@@ -1631,7 +1699,6 @@ def generate_c_test_boilerplate(services, defines, file_crc, module, plugin, str
 #undef vl_calsizefun
 
 /* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
 #define vl_printfun
 #include "{module}.api.h"
 #undef vl_printfun
@@ -1678,27 +1745,28 @@ def generate_c_test_boilerplate(services, defines, file_crc, module, plugin, str
                 continue
             write("static void\n")
             write("vl_api_{n}_t_handler (vl_api_{n}_t * mp) {{\n".format(n=e))
-            write('    vl_print(0, "{n} event called:");\n'.format(n=e))
-            write("    vl_api_{n}_t_print(mp, 0);\n".format(n=e))
+            write('    vlib_cli_output(0, "{n} event called:");\n'.format(n=e))
+            write(
+                '    vlib_cli_output(0, "%U", vl_api_{n}_t_format, mp);\n'.format(n=e)
+            )
             write("}\n")
 
     write("static void\n")
     write("setup_message_id_table (vat_main_t * vam, u16 msg_id_base) {\n")
     for s in services:
         write(
-            "   vl_msg_api_set_handlers(VL_API_{ID} + msg_id_base, "
-            '                           "{n}",\n'
-            "                           vl_api_{n}_t_handler, "
-            "                           vl_noop_handler,\n"
-            "                           vl_api_{n}_t_endian, "
-            "                           vl_api_{n}_t_print,\n"
-            "                           sizeof(vl_api_{n}_t), 1,\n"
-            "                           vl_api_{n}_t_print_json,\n"
-            "                           vl_api_{n}_t_tojson,\n"
-            "                           vl_api_{n}_t_fromjson,\n"
-            "                           vl_api_{n}_t_calc_size);\n".format(
-                n=s.reply, ID=s.reply.upper()
-            )
+            "   vl_msg_api_config (&(vl_msg_api_msg_config_t){{\n"
+            "    .id = VL_API_{ID} + msg_id_base,\n"
+            '    .name = "{n}",\n'
+            "    .handler = vl_api_{n}_t_handler,\n"
+            "    .endian = vl_api_{n}_t_endian,\n"
+            "    .format_fn = vl_api_{n}_t_format,\n"
+            "    .size = sizeof(vl_api_{n}_t),\n"
+            "    .traced = 1,\n"
+            "    .tojson = vl_api_{n}_t_tojson,\n"
+            "    .fromjson = vl_api_{n}_t_fromjson,\n"
+            "    .calc_size = vl_api_{n}_t_calc_size,\n"
+            "   }});".format(n=s.reply, ID=s.reply.upper())
         )
         write(
             '   hash_set_mem (vam->function_by_name, "{n}", api_{n});\n'.format(
@@ -1717,19 +1785,18 @@ def generate_c_test_boilerplate(services, defines, file_crc, module, plugin, str
         # Events
         for e in s.events:
             write(
-                "   vl_msg_api_set_handlers(VL_API_{ID} + msg_id_base, "
-                '                          "{n}",\n'
-                "                           vl_api_{n}_t_handler, "
-                "                           vl_noop_handler,\n"
-                "                           vl_api_{n}_t_endian, "
-                "                           vl_api_{n}_t_print,\n"
-                "                           sizeof(vl_api_{n}_t), 1,\n"
-                "                           vl_api_{n}_t_print_json,\n"
-                "                           vl_api_{n}_t_tojson,\n"
-                "                           vl_api_{n}_t_fromjson,\n"
-                "                           vl_api_{n}_t_calc_size);\n".format(
-                    n=e, ID=e.upper()
-                )
+                "   vl_msg_api_config (&(vl_msg_api_msg_config_t){{\n"
+                "    .id = VL_API_{ID} + msg_id_base,\n"
+                '    .name = "{n}",\n'
+                "    .handler = vl_api_{n}_t_handler,\n"
+                "    .endian = vl_api_{n}_t_endian,\n"
+                "    .format_fn = vl_api_{n}_t_format,\n"
+                "    .size = sizeof(vl_api_{n}_t),\n"
+                "    .traced = 1,\n"
+                "    .tojson = vl_api_{n}_t_tojson,\n"
+                "    .fromjson = vl_api_{n}_t_fromjson,\n"
+                "    .calc_size = vl_api_{n}_t_calc_size,\n"
+                "   }});".format(n=e, ID=e.upper())
             )
 
     write("}\n")
@@ -1968,7 +2035,6 @@ def generate_c_test2_boilerplate(services, defines, module, stream):
 #include "{module}.api.h"
 #undef vl_calsizefun
 
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
 #define vl_printfun
 #include "{module}.api.h"
 #undef vl_printfun
@@ -2010,24 +2076,24 @@ def generate_c_test2_boilerplate(services, defines, module, stream):
 #
 # Plugin entry point
 #
-def run(args, apifilename, s):
+def run(output_dir, apifilename, s):
     """Main plugin entry point."""
     stream = StringIO()
 
-    if not args.outputdir:
+    if not output_dir:
         sys.stderr.write("Missing --outputdir argument")
         return None
 
     basename = os.path.basename(apifilename)
     filename, _ = os.path.splitext(basename)
     modulename = filename.replace(".", "_")
-    filename_enum = os.path.join(args.outputdir + "/" + basename + "_enum.h")
-    filename_types = os.path.join(args.outputdir + "/" + basename + "_types.h")
-    filename_c = os.path.join(args.outputdir + "/" + basename + ".c")
-    filename_c_test = os.path.join(args.outputdir + "/" + basename + "_test.c")
-    filename_c_test2 = os.path.join(args.outputdir + "/" + basename + "_test2.c")
-    filename_c_tojson = os.path.join(args.outputdir + "/" + basename + "_tojson.h")
-    filename_c_fromjson = os.path.join(args.outputdir + "/" + basename + "_fromjson.h")
+    filename_enum = os.path.join(output_dir + "/" + basename + "_enum.h")
+    filename_types = os.path.join(output_dir + "/" + basename + "_types.h")
+    filename_c = os.path.join(output_dir + "/" + basename + ".c")
+    filename_c_test = os.path.join(output_dir + "/" + basename + "_test.c")
+    filename_c_test2 = os.path.join(output_dir + "/" + basename + "_test2.c")
+    filename_c_tojson = os.path.join(output_dir + "/" + basename + "_tojson.h")
+    filename_c_fromjson = os.path.join(output_dir + "/" + basename + "_fromjson.h")
 
     # Generate separate types file
     st = StringIO()