]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2373 in SNORT/snort3 from ~OSERHIIE/snort3:help_modules_json...
authorBhagya Tholpady (bbantwal) <bbantwal@cisco.com>
Mon, 10 Aug 2020 15:33:37 +0000 (15:33 +0000)
committerBhagya Tholpady (bbantwal) <bbantwal@cisco.com>
Mon, 10 Aug 2020 15:33:37 +0000 (15:33 +0000)
Squashed commit of the following:

commit 87484e324090b3d0baa60e5a51bb4f8bc0743890
Author: Oleksandr Serhiienko <oserhiie@cisco.com>
Date:   Mon Aug 3 23:00:45 2020 +0300

    cip: fix the trailing parameter for the module

commit 31bdafe40d97c6b6a37b23fda5c140ebc3c170f8
Author: Oleksandr Serhiienko <oserhiie@cisco.com>
Date:   Tue Jul 28 23:34:13 2020 +0300

    main: add printing modules help in JSON format

        * main: new CLI option '--help-modules-json' is presented
        * main: add support for HelpType HT_HMO_JSON
        * framework: add API to expand maxN literals for ranges in Parameter
        * managers: add JSON dumpers into ModuleManager
        * managers: rename 'What' -> 'Help' in the output of '--help-module' CLI option

commit 87139f76fa31f68a1b796206e6201afb752d9bb6
Author: Oleksandr Serhiienko <oserhiie@cisco.com>
Date:   Tue Jul 28 17:18:54 2020 +0300

    helpers: extend printed JSON syntax

        * anonymous arrays (root arrays)
        * JSON keywords (null, true, false)
        * printing values of floating point type

src/framework/parameter.cc
src/framework/parameter.h
src/helpers/json_stream.cc
src/helpers/json_stream.h
src/helpers/test/json_stream_test.cc
src/main/help.cc
src/main/help.h
src/main/snort_module.cc
src/managers/module_manager.cc
src/managers/module_manager.h
src/service_inspectors/cip/cip_module.cc

index 2471b7f1ab590544f77b43995d06943aeeb7d392..ec15144ffa18de4269bef8d18dd3587bec69278a 100644 (file)
@@ -99,7 +99,7 @@ static size_t split(const string& txt, vector<string>& strs)
     return strs.size();
 }
 
-static int64_t get_int(const char* r)
+int64_t Parameter::get_int(const char* r)
 {
     if ( *r == 'm' )
     {
@@ -153,7 +153,7 @@ static bool valid_int(Value& v, const char* r)
 
     if ( *r != ':' )
     {
-        int64_t low = get_int(r);
+        int64_t low = Parameter::get_int(r);
 
         if ( d < low )
             return false;
@@ -164,7 +164,7 @@ static bool valid_int(Value& v, const char* r)
 
     if ( t and *++t )
     {
-        int64_t hi = get_int(t);
+        int64_t hi = Parameter::get_int(t);
 
         if ( d > hi )
             return false;
@@ -693,14 +693,14 @@ TEST_CASE("string", "[Parameter]")
 
 TEST_CASE("max", "[Parameter]")
 {
-    CHECK(get_int("max31") == 2147483647);
-    CHECK(get_int("max32") == 4294967295);
-    CHECK(get_int("max53") == 9007199254740992);
+    CHECK(Parameter::get_int("max31") == 2147483647);
+    CHECK(Parameter::get_int("max32") == 4294967295);
+    CHECK(Parameter::get_int("max53") == 9007199254740992);
 
     if ( sizeof(size_t) == 4 )
-        CHECK(get_int("maxSZ") == 4294967295);
+        CHECK(Parameter::get_int("maxSZ") == 4294967295);
     else
-        CHECK(get_int("maxSZ") == 9007199254740992);
+        CHECK(Parameter::get_int("maxSZ") == 9007199254740992);
 }
 
 #endif
index bfe8a5f81341dc7fa90657b4af89eeeca136768a..439a021e206f58dea135554cb7bbc3417dfe8db2 100644 (file)
@@ -96,6 +96,9 @@ struct SO_PUBLIC Parameter
 
     // 0-based; -1 if not found; list is | delimited
     static int index(const char* list, const char* key);
+
+    // convert string to long (including 'maxN' literals)
+    static int64_t get_int(const char*);
 };
 }
 #endif
index be554bdbf071b113054da76d5c272d3a0995ed22..545c37ebff02ca7d9bbac8ad5ea7f1462d87a312 100644 (file)
@@ -41,9 +41,10 @@ void JsonStream::open(const char* key)
 void JsonStream::close()
 {
     out << " }";
+    sep = true;
     assert(level > 0);
 
-    if ( --level == 0 )
+    if ( --level == 0 and !level_array )
     {
         out << std::endl;
         sep = false;
@@ -53,14 +54,36 @@ void JsonStream::close()
 void JsonStream::open_array(const char* key)
 {
     split();
-    out << std::quoted(key) << ": [ ";
+
+    if ( key )
+        out << std::quoted(key) << ": ";
+
+    out << "[ ";
     sep = false;
+    level_array++;
 }
 
 void JsonStream::close_array()
 {
     out << " ]";
     sep = true;
+    assert(level_array > 0);
+
+    if ( --level_array == 0 and !level )
+    {
+        out << std::endl;
+        sep = false;
+    }
+}
+
+void JsonStream::put(const char* key)
+{
+    split();
+
+    if ( key )
+        out << std::quoted(key) << ": ";
+
+    out << "null";
 }
 
 void JsonStream::put(const char* key, long val)
@@ -86,6 +109,37 @@ void JsonStream::put(const char* key, const std::string& val)
     out << std::quoted(val);
 }
 
+void JsonStream::put(const char* key, double val, int precision)
+{
+    split();
+
+    if ( key )
+        out << std::quoted(key) << ": ";
+
+    out.precision(precision);
+    out << std::fixed << val;
+}
+
+void JsonStream::put_true(const char* key)
+{
+    split();
+
+    if ( key )
+        out << std::quoted(key) << ": ";
+
+    out << "true";
+}
+
+void JsonStream::put_false(const char* key)
+{
+    split();
+
+    if ( key )
+        out << std::quoted(key) << ": ";
+
+    out << "false";
+}
+
 void JsonStream::split()
 {
     if ( sep )
index 7a4cf865656e50e814caaf86e9fe83ea7d72d640..211656035a97bffb69a01e8b531eb0b76d86b084 100644 (file)
@@ -33,11 +33,16 @@ public:
     void open(const char* key = nullptr);
     void close();
 
-    void open_array(const char* key);
+    void open_array(const char* key = nullptr);
     void close_array();
 
+    void put(const char* key);    // null
     void put(const char* key, long val);
     void put(const char* key, const std::string& val);
+    void put(const char* key, double val, int precision);
+
+    void put_true(const char* key);
+    void put_false(const char* key);
 
 private:
     void split();
@@ -46,6 +51,7 @@ private:
     std::ostream& out;
     bool sep = false;
     unsigned level = 0;
+    unsigned level_array = 0;
 };
 
 #endif
index be07e667142a7f084a5d6763940b89808e73083f..e59c278aed0f7ad7b896b2060eac392971adfcbf 100644 (file)
@@ -39,11 +39,52 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == "{  }\n");
     }
 
+    SECTION("empty root array")
+    {
+        js.open_array();
+        js.close_array();
+        const char* x = R"-([  ])-" "\n";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("empty object")
+    {
+        js.open();
+        js.open("o");
+        js.close();
+        js.close();
+        const char* x = R"-({ "o": {  } })-" "\n";
+        CHECK(ss.str() == x);
+    }
+
     SECTION("empty array")
     {
+        js.open();
         js.open_array("a");
         js.close_array();
-        const char* x = R"-("a": [  ])-";
+        js.close();
+        const char* x = R"-({ "a": [  ] })-" "\n";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("null")
+    {
+        js.put("n");
+        const char* x = R"-("n": null)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("bool true")
+    {
+        js.put_true("b");
+        const char* x = R"-("b": true)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("bool false")
+    {
+        js.put_false("b");
+        const char* x = R"-("b": false)-";
         CHECK(ss.str() == x);
     }
 
@@ -54,6 +95,13 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == x);
     }
 
+    SECTION("real")
+    {
+        js.put("r", 2.5, 2);
+        const char* x = R"-("r": 2.50)-";
+        CHECK(ss.str() == x);
+    }
+
     SECTION("string")
     {
         js.put("s", "yo");
@@ -68,12 +116,36 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == "");
     }
 
+    SECTION("null item")
+    {
+        js.put(nullptr);
+        CHECK(ss.str() == "null");
+    }
+
+    SECTION("bool true item")
+    {
+        js.put_true(nullptr);
+        CHECK(ss.str() == "true");
+    }
+
+    SECTION("bool false item")
+    {
+        js.put_false(nullptr);
+        CHECK(ss.str() == "false");
+    }
+
     SECTION("int item")
     {
         js.put(nullptr, 1);
         CHECK(ss.str() == "1");
     }
 
+    SECTION("real item")
+    {
+        js.put(nullptr, 2.5, 2);
+        CHECK(ss.str() == "2.50");
+    }
+
     SECTION("string item")
     {
         js.put(nullptr, "it");
@@ -89,6 +161,22 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == x);
     }
 
+    SECTION("null list")
+    {
+        js.put("i");
+        js.put("j");
+        const char* x = R"-("i": null, "j": null)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("bool list")
+    {
+        js.put_true("i");
+        js.put_false("j");
+        const char* x = R"-("i": true, "j": false)-";
+        CHECK(ss.str() == x);
+    }
+
     SECTION("int list")
     {
         js.put("i", 2);
@@ -118,6 +206,32 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == x);
     }
 
+    SECTION("null array")
+    {
+        js.open();
+        js.open_array("n");
+        js.put(nullptr);
+        js.put(nullptr);
+        js.put(nullptr);
+        js.close_array();
+        js.close();
+        const char* x = R"-({ "n": [ null, null, null ] })-" "\n";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("bool array")
+    {
+        js.open();
+        js.open_array("b");
+        js.put_true(nullptr);
+        js.put_false(nullptr);
+        js.put_true(nullptr);
+        js.close_array();
+        js.close();
+        const char* x = R"-({ "b": [ true, false, true ] })-" "\n";
+        CHECK(ss.str() == x);
+    }
+
     SECTION("int array")
     {
         js.open();
@@ -131,27 +245,27 @@ TEST_CASE("basic", "[json_stream]")
         CHECK(ss.str() == x);
     }
 
-    SECTION("string array")
+    SECTION("real array")
     {
         js.open();
-        js.open_array("v");
-        js.put(nullptr, "long");
-        js.put(nullptr, "road");
+        js.open_array("r");
+        js.put(nullptr, 2.556, 3);
+        js.put(nullptr, 3.7778, 4);
         js.close_array();
         js.close();
-        const char* x = R"-({ "v": [ "long", "road" ] })-" "\n";
+        const char* x = R"-({ "r": [ 2.556, 3.7778 ] })-" "\n";
         CHECK(ss.str() == x);
     }
 
-    SECTION("array list")
+    SECTION("string array")
     {
         js.open();
-        js.open_array("m");
-        js.close_array();
-        js.open_array("n");
+        js.open_array("v");
+        js.put(nullptr, "long");
+        js.put(nullptr, "road");
         js.close_array();
         js.close();
-        const char* x = R"-({ "m": [  ], "n": [  ] })-" "\n";
+        const char* x = R"-({ "v": [ "long", "road" ] })-" "\n";
         CHECK(ss.str() == x);
     }
 
@@ -178,5 +292,72 @@ TEST_CASE("basic", "[json_stream]")
         const char* x = R"-({ "c": "Snort", "n": [  ], "d": "++" })-" "\n";
         CHECK(ss.str() == x);
     }
+
+    SECTION("root array of objects")
+    {
+        js.open_array();
+        js.open();
+        js.close();
+        js.open();
+        js.close();
+        js.open();
+        js.close();
+        js.close_array();
+        const char* x = R"-([ {  }, {  }, {  } ])-" "\n";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("root array of objects with nested arrays of objects")
+    {
+        js.open_array();
+        js.open();
+        js.put("i", 1);
+        js.open_array("array_1");
+        js.open();
+        js.put("str", "Snort");
+        js.close();
+        js.open();
+        js.put("str", "++");
+        js.close();
+        js.close_array();
+        js.put("j", 2);
+        js.close();
+        js.open();
+        js.put("i", 3);
+        js.open_array("array_2");
+        js.open();
+        js.put("str", "IPS");
+        js.close();
+        js.open();
+        js.put("str", "IDS");
+        js.close();
+        js.close_array();
+        js.put("j", 4);
+        js.close();
+        js.close_array();
+        const char* x = R"-([ { "i": 1, "array_1": [ { "str": "Snort" }, { "str": "++" } ],)-"
+            R"-( "j": 2 }, { "i": 3, "array_2": [ { "str": "IPS" }, { "str": "IDS" } ],)-"
+            R"-( "j": 4 } ])-" "\n";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("root object with nested objects")
+    {
+        js.open();
+        js.open_array("keys");
+        js.put(nullptr, "name");
+        js.put(nullptr, "version");
+        js.close_array();
+        js.open("name");
+        js.put("value", "Snort");
+        js.close();
+        js.open("version");
+        js.put("value", "3.0.0");
+        js.close();
+        js.close();
+        const char* x = R"-({ "keys": [ "name", "version" ], "name": { "value": "Snort" },)-"
+            R"-( "version": { "value": "3.0.0" } })-" "\n";
+        CHECK(ss.str() == x);
+    }
 }
 
index 0743f76b919b92d7556feddb28363d5a254c691e..ac0116b59d82484f7ef6e0eb4afc85787b683c8d 100644 (file)
@@ -55,6 +55,7 @@ static constexpr const char* snort_help =
     "--help-limits print the int upper bounds denoted by max*\n"
     "--help-module <module> output description of given module\n"
     "--help-modules list all available modules with brief help\n"
+    "--help-modules-json dump description of all available modules in JSON format\n"
     "--help-plugins list all available plugins with brief help\n"
     "--help-options [<option prefix>] output matching command line options\n"
     "--help-signals dump available control signals\n"
@@ -146,7 +147,7 @@ void help_args(const char* pfx)
 enum HelpType
 {
     HT_BUF, HT_CFG, HT_CMD, HT_DBR, HT_DDR,
-    HT_GID, HT_HMO, HT_HPL, HT_DFL,
+    HT_GID, HT_HMO, HT_HMO_JSON, HT_HPL, HT_DFL,
     HT_IPS, HT_LST, HT_MOD, HT_PEG, HT_PLG
 };
 
@@ -184,6 +185,9 @@ enum HelpType
     case HT_HMO:
         ModuleManager::show_modules();
         break;
+    case HT_HMO_JSON:
+        ModuleManager::show_modules_json();
+        break;
     case HT_HPL:
         PluginManager::show_plugins();
         break;
@@ -265,6 +269,11 @@ void config_markup(SnortConfig*, const char*)
     show_help(sc, val, HT_HMO);
 }
 
+[[noreturn]] void help_modules_json(SnortConfig* sc, const char* val)
+{
+    show_help(sc, val, HT_HMO_JSON);
+}
+
 [[noreturn]] void help_plugins(SnortConfig* sc, const char* val)
 {
     show_help(sc, val, HT_HPL);
index c51cfa20d35ae18598878d9bc416a4d920cca914..b4c6d6be59e4ab0eeed9c66f0e9759860179aa84 100644 (file)
@@ -42,6 +42,7 @@ void help_args(const char* pfx);
 [[noreturn]] void help_limits(snort::SnortConfig* sc, const char*);
 [[noreturn]] void help_module(snort::SnortConfig* sc, const char*);
 [[noreturn]] void help_modules(snort::SnortConfig* sc, const char*);
+[[noreturn]] void help_modules_json(snort::SnortConfig* sc, const char*);
 [[noreturn]] void help_options(snort::SnortConfig*, const char*);
 [[noreturn]] void help_plugins(snort::SnortConfig* sc, const char*);
 [[noreturn]] void help_signals(snort::SnortConfig*, const char*);
index 5de0697eeed9ad92913d33b6141d56e21c8f087c..dcbff4e200b77930b375378eeed6e28415f9d8a2 100644 (file)
@@ -396,6 +396,9 @@ static const Parameter s_params[] =
     { "--help-modules", Parameter::PT_IMPLIED, nullptr, nullptr,
       "list all available modules with brief help" },
 
+    { "--help-modules-json", Parameter::PT_IMPLIED, nullptr, nullptr,
+      "dump description of all available modules in JSON format" },
+
     { "--help-options", Parameter::PT_STRING, "(optional)", nullptr,
       "[<option prefix>] output matching command line option quick help (same as -?)" },
 
@@ -929,6 +932,9 @@ bool SnortModule::set(const char*, Value& v, SnortConfig* sc)
     else if ( v.is("--help-modules") )
         help_modules(sc, v.get_string());
 
+    else if ( v.is("--help-modules-json") )
+        help_modules_json(sc, v.get_string());
+
     else if ( v.is("--help-options") )
         help_options(sc, v.get_string());
 
index f3331d3b3bed9a7c5d8fae29889ab6a97b1c935c..531844c1859e8bbb08ed9d06374547394b0be447 100644 (file)
@@ -35,6 +35,7 @@
 
 #include "framework/base_api.h"
 #include "framework/module.h"
+#include "helpers/json_stream.h"
 #include "helpers/markup.h"
 #include "log/messages.h"
 #include "main/modules.h"
@@ -1061,7 +1062,7 @@ void ModuleManager::show_module(const char* name)
         cout << endl << Markup::head(3) << name << endl << endl;
 
         if ( const char* h = m->get_help() )
-            cout << endl << "What: " << h << endl;
+            cout << endl << "Help: " << h << endl;
 
         cout << endl << "Type: "  << mod_type(mh->api) << endl;
         cout << endl << "Usage: "  << mod_use(m->get_usage()) << endl;
@@ -1261,9 +1262,9 @@ static const char* peg_op(CountType ct)
 {
     switch ( ct )
     {
-    case CountType::SUM: return " (sum)";
-    case CountType::NOW: return " (now)";
-    case CountType::MAX: return " (max)";
+    case CountType::SUM: return "sum";
+    case CountType::NOW: return "now";
+    case CountType::MAX: return "max";
     default: break;
     }
     assert(false);
@@ -1297,7 +1298,7 @@ void ModuleManager::show_pegs(const char* pfx, bool exact)
             cout << "." << pegs->name;
             cout << Markup::emphasis_off();
             cout << ": " << pegs->help;
-            cout << peg_op(pegs->type);
+            cout << " (" << peg_op(pegs->type) << ")";
             cout << endl;
             ++pegs;
         }
@@ -1574,3 +1575,376 @@ void ModuleManager::show_rules(const char* pfx, bool exact)
         cout << "no match" << endl;
 }
 
+//--------------------------------------------------------------------------
+// JSON dumpers
+//--------------------------------------------------------------------------
+
+static void dump_param_range_json(JsonStream& json, const Parameter* p)
+{
+    const char* range = p->get_range();
+
+    if ( !range )
+        json.put("range");
+    else
+    {
+        switch ( p->type )
+        {
+        case Parameter::PT_INT:
+        case Parameter::PT_PORT:
+        {
+            std::string tr = range;
+            const char* d = strchr(range, ':');
+            if ( *range == 'm' )
+            {
+                if ( d )
+                {
+                    tr = std::to_string(Parameter::get_int(range)) +
+                        tr.substr(tr.find(":"));
+                }
+                else
+                    tr = std::to_string(Parameter::get_int(range));
+            }
+            if ( d and *++d == 'm' )
+            {
+                tr = tr.substr(0, tr.find(":") + 1) +
+                    std::to_string(Parameter::get_int(d));
+            }
+            json.put("range", tr);
+            break;
+        }
+
+        default:
+            json.put("range", p->get_range());
+        }
+    }
+}
+
+static void dump_param_default_json(JsonStream& json, const Parameter* p)
+{
+    const char* def = p->deflt;
+
+    if ( !def )
+        json.put("default");
+    else
+    {
+        switch ( p->type )
+        {
+        case Parameter::PT_INT:
+        case Parameter::PT_PORT:
+            json.put("default", std::stol(def));
+            break;
+
+        case Parameter::PT_REAL:
+        {
+            const char* dot = strchr(def, '.');
+            if ( dot )
+                json.put("default", std::stod(def), strlen(dot) - 1);
+            else
+                json.put("default", std::stod(def));
+
+            break;
+        }
+
+        case Parameter::PT_BOOL:
+            !strcmp(def, "true") ? json.put_true("default") : json.put_false("default");
+            break;
+
+        default:
+            json.put("default", def);
+        }
+    }
+}
+
+static void dump_params_tree_json(JsonStream& json, const Parameter* p)
+{
+    while ( p and p->type != Parameter::PT_MAX )
+    {
+        assert(p->name);
+
+        json.open();
+        json.put("option", p->name);
+        json.put("type", p->get_type());
+        if ( p->is_table() and p->range )
+        {
+            json.open_array("sub_options");
+            dump_params_tree_json(json, (const Parameter*)p->range);
+            json.close_array();
+        }
+        else
+            dump_param_range_json(json, p);
+
+        dump_param_default_json(json, p);
+        if ( p->help )
+            json.put("help", p->help);
+        else
+            json.put("help");
+
+        json.close();
+
+        ++p;
+    }
+}
+
+static void dump_configs_json(JsonStream& json, const Module* mod)
+{
+    const Parameter* params = mod->get_parameters();
+
+    json.open_array("configuration");
+    dump_params_tree_json(json, params);
+    json.close_array();
+}
+
+static void dump_commands_json(JsonStream& json, const Module* mod)
+{
+    const Command* cmds = mod->get_commands();
+
+    json.open_array("commands");
+
+    while ( cmds and cmds->name )
+    {
+        json.open();
+
+        json.put("name", cmds->name);
+
+        json.open_array("params");
+        if ( cmds->params )
+            dump_params_tree_json(json, cmds->params);
+
+        json.close_array();
+
+        if ( cmds->help )
+            json.put("help", cmds->help);
+        else
+            json.put("help");
+
+        json.close();
+
+        ++cmds;
+    }
+
+    json.close_array();
+}
+
+static void dump_rules_json(JsonStream& json, const Module* mod)
+{
+    auto rules = get_rules(mod->get_name(), true);
+
+    json.open_array("rules");
+    for ( const auto& rp : rules )
+    {
+        json.open();
+
+        json.put("gid", rp.mod->get_gid());
+        json.put("sid", rp.rule->sid);
+        json.put("msg", rp.rule->msg);
+
+        json.close();
+    }
+    json.close_array();
+}
+
+static void dump_pegs_json(JsonStream& json, const Module* mod)
+{
+    const PegInfo* pegs = mod->get_pegs();
+
+    json.open_array("peg_counts");
+    while ( pegs and pegs->type != CountType::END )
+    {
+        json.open();
+        json.put("type", peg_op(pegs->type));
+
+        assert(pegs->name);
+        json.put("name", pegs->name);
+
+        if ( pegs->help )
+            json.put("help", pegs->help);
+        else
+            json.put("help");
+
+        json.close();
+
+        ++pegs;
+    }
+    json.close_array();
+}
+
+void ModuleManager::show_modules_json()
+{
+    auto mod_hooks = get_all_modhooks();
+    mod_hooks.sort(comp_mods);
+    JsonStream json(std::cout);
+
+    json.open_array();
+    for ( const auto* mh : mod_hooks )
+    {
+        const Module* mod = mh->mod;
+        assert(mod);
+
+        std::string name = "";
+        if ( const char* n = mod->get_name() )
+            name = n;
+
+        assert(!name.empty());
+
+        std::string help = "";
+        if ( const char* h = mod->get_help() )
+            help = h;
+
+        const char* type = mod_type(mh->api);
+        const char* usage = mod_use(mod->get_usage());
+
+        json.open();
+        json.put("module", name);
+        json.put("help", help);
+        json.put("type", type);
+        json.put("usage", usage);
+        if ( mh->api and (mh->api->type == PT_INSPECTOR) )
+            json.put("instance_type", mod_bind(mod));
+
+        dump_configs_json(json, mod);
+        dump_commands_json(json, mod);
+        dump_rules_json(json, mod);
+        dump_pegs_json(json, mod);
+        json.close();
+    }
+    json.close_array();
+}
+
+#ifdef UNIT_TEST
+
+#include <catch/snort_catch.h>
+
+TEST_CASE("param range JSON dumper", "[ModuleManager]")
+{
+    std::stringstream ss;
+    JsonStream json(ss);
+
+    SECTION("null")
+    {
+        const Parameter p("string", Parameter::PT_STRING, nullptr, nullptr, "help");
+        dump_param_range_json(json, &p);
+        std::string x = R"-("range": null)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("common string")
+    {
+        const Parameter p("enum", Parameter::PT_ENUM, "one | two | three", nullptr, "help");
+        dump_param_range_json(json, &p);
+        std::string x = R"-("range": "one | two | three")-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("number string")
+    {
+        const Parameter i_max("int_max", Parameter::PT_INT, "255", nullptr, "help");
+        dump_param_range_json(json, &i_max);
+        std::string x = R"-("range": "255")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter i_min("int_min", Parameter::PT_INT, "255:", nullptr, "help");
+        dump_param_range_json(json, &i_min);
+        x = R"-(, "range": "255:")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter i_exp_max("int_exp_max", Parameter::PT_INT, ":255", nullptr, "help");
+        dump_param_range_json(json, &i_exp_max);
+        x = R"-(, "range": ":255")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter p_min_max("int_min_max", Parameter::PT_PORT, "0:65535", nullptr, "help");
+        dump_param_range_json(json, &p_min_max);
+        x = R"-(, "range": "0:65535")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter i_hex("int_in_hex", Parameter::PT_INT, "0x5:0xFF", nullptr, "help");
+        dump_param_range_json(json, &i_hex);
+        x = R"-(, "range": "0x5:0xFF")-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("number string with maxN")
+    {
+        const Parameter i_max("int_max", Parameter::PT_INT, "max32", nullptr, "help");
+        dump_param_range_json(json, &i_max);
+        std::string x = R"-("range": "4294967295")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter i_min("int_min", Parameter::PT_INT, "max32:", nullptr, "help");
+        dump_param_range_json(json, &i_min);
+        x = R"-(, "range": "4294967295:")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter i_exp_max("int_exp_max", Parameter::PT_INT, ":max32", nullptr, "help");
+        dump_param_range_json(json, &i_exp_max);
+        x = R"-(, "range": ":4294967295")-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter p_min_max("int_min_max", Parameter::PT_INT, "max31:max32", nullptr, "help");
+        dump_param_range_json(json, &p_min_max);
+        x = R"-(, "range": "2147483647:4294967295")-";
+        CHECK(ss.str() == x);
+    }
+}
+
+TEST_CASE("param default JSON dumper", "[ModuleManager]")
+{
+    std::stringstream ss;
+    JsonStream json(ss);
+
+    SECTION("null")
+    {
+        const Parameter p("int", Parameter::PT_INT, nullptr, nullptr, "help");
+        dump_param_default_json(json, &p);
+        std::string x = R"-("default": null)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("string")
+    {
+        const Parameter p("multi", Parameter::PT_MULTI, "one | two | three", "one two", "help");
+        dump_param_default_json(json, &p);
+        std::string x = R"-("default": "one two")-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("integer")
+    {
+        const Parameter p("int", Parameter::PT_INT, nullptr, "5", "help");
+        dump_param_default_json(json, &p);
+        std::string x = R"-("default": 5)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("real")
+    {
+        const Parameter p("real", Parameter::PT_REAL, nullptr, "12.345", "help");
+        dump_param_default_json(json, &p);
+        std::string x = R"-("default": 12.345)-";
+        CHECK(ss.str() == x);
+    }
+
+    SECTION("boolean")
+    {
+        const Parameter t("bool_true", Parameter::PT_BOOL, nullptr, "true", "help");
+        dump_param_default_json(json, &t);
+        std::string x = R"-("default": true)-";
+        CHECK(ss.str() == x);
+        ss.str("");
+
+        const Parameter f("bool_false", Parameter::PT_BOOL, nullptr, "false", "help");
+        dump_param_default_json(json, &f);
+        x = R"-(, "default": false)-";
+        CHECK(ss.str() == x);
+    }
+}
+
+#endif // UNIT_TEST
+
index 5bf4657fdc5291c87d0871c69481c55bd552c70f..38b2c63740fe43ca89484bb8c9a5c619d106c5ed 100644 (file)
@@ -58,6 +58,7 @@ public:
     static void list_modules(const char* = nullptr);
     static void dump_modules();
     static void show_modules();
+    static void show_modules_json();
     static void show_module(const char*);
 
     static bool gid_in_use(uint32_t);
index 00a4dcf21143ab71e83ac8769eccf1d9a3fd1555..45c014aa8611bb0154e7e2e7c1b73f84a4e07023 100644 (file)
@@ -47,7 +47,7 @@ static const Parameter c_params[] =
       "max cip connections" },
     { "max_unconnected_messages", Parameter::PT_INT, "1:10000", "100",
       "max unconnected cip messages" },
-    { nullptr, Parameter::PT_STRING, nullptr, nullptr, nullptr }
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
 };
 
 static const RuleMap cip_rules[] =