return 1;
}
+static int set_user_detector_data_item(lua_State *L)
+{
+ auto& ud = *UserData<LuaObject>::check(L, DETECTOR, 1);
+ const char* table = lua_tostring(L, 2);
+ if (!table)
+ {
+ APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data table string in %s.\n",
+ ud->get_detector()->get_name().c_str());
+ return 0;
+ }
+ const char* key = lua_tostring(L, 3);
+ if (!key)
+ {
+ APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data key string in %s.\n",
+ ud->get_detector()->get_name().c_str());
+ return 0;
+ }
+
+ const char* item = lua_tostring(L, 4);
+ if (!item)
+ {
+ APPID_LOG(nullptr, TRACE_ERROR_LEVEL, "appid: Invalid detector data item string in %s.\n",
+ ud->get_detector()->get_name().c_str());
+ return 0;
+ }
+
+ int result = ud->get_odp_ctxt().get_user_data_map().add_user_data(table, key, item, true) ? 1 : 0;
+
+ return result;
+}
+
static const luaL_Reg detector_methods[] =
{
/* Obsolete API names. No longer use these! They are here for backward
{ "getHttpTunneledPort", get_http_tunneled_port },
{ "getUserDetectorDataItem", get_user_detector_data_item },
+ { "setUserDetectorDataItem", set_user_detector_data_item },
/* CIP registration */
{"addCipConnectionClass", detector_add_cip_connection_class},
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2018-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// user_data_map_test.cc author Oleksandr Stepanov <ostepano@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "main/thread.h"
+
+#include "../user_data_map.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+static uint appid_log_call_count = 0;
+void appid_log(const snort::Packet*, unsigned char, char const*, ...) { appid_log_call_count++; }
+
+SThreadType test_thread_type = SThreadType::STHREAD_TYPE_MAIN;
+
+namespace snort
+{
+ SThreadType get_thread_type()
+ {
+ return test_thread_type;
+ }
+}
+
+TEST_GROUP(user_data_map_test)
+{
+ UserDataMap* test_user_data_map;
+
+ void setup() override
+ {
+ test_user_data_map = new UserDataMap();
+ test_user_data_map->set_configuration_completed(false);
+ }
+
+ void teardown() override
+ {
+ test_user_data_map->set_configuration_completed(false);
+ delete test_user_data_map;
+ }
+};
+
+TEST(user_data_map_test, add_and_get_user_data)
+{
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value = "test_value";
+
+ bool added = test_user_data_map->add_user_data(table, key, value);
+ CHECK_TRUE(added);
+
+ const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key);
+ STRCMP_EQUAL(value.c_str(), retrieved_value);
+}
+
+TEST(user_data_map_test, add_duplicate_key_without_override)
+{
+ appid_log_call_count = 0;
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value1 = "test_value1";
+ const std::string value2 = "test_value2";
+
+ bool first_add = test_user_data_map->add_user_data(table, key, value1);
+ CHECK_TRUE(first_add);
+
+ bool second_add = test_user_data_map->add_user_data(table, key, value2);
+ CHECK_EQUAL(1, appid_log_call_count);
+ CHECK_FALSE(second_add);
+
+ auto get_value = test_user_data_map->get_user_data_value_str(table, key);
+ STRCMP_EQUAL(get_value, value1.c_str());
+}
+
+TEST(user_data_map_test, add_duplicate_key_with_override)
+{
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value1 = "test_value1";
+ const std::string value2 = "test_value2";
+
+ bool first_add = test_user_data_map->add_user_data(table, key, value1);
+ CHECK_TRUE(first_add);
+
+ bool second_add = test_user_data_map->add_user_data(table, key, value2, true);
+ const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key);
+ CHECK_TRUE(second_add);
+ STRCMP_EQUAL(value2.c_str(), retrieved_value);
+}
+
+TEST(user_data_map_test, get_nonexistent_key)
+{
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value = "test_value";
+
+ test_user_data_map->add_user_data(table, key, value);
+
+ const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, "some_other_key");
+ CHECK_TRUE(retrieved_value == nullptr);
+}
+
+TEST(user_data_map_test, get_from_nonexistent_table)
+{
+ const std::string table = "nonexistent_table";
+ const std::string key = "test_key";
+
+ const char* retrieved_value = test_user_data_map->get_user_data_value_str(table, key);
+ CHECK_TRUE(retrieved_value == nullptr);
+}
+
+TEST(user_data_map_test, add_user_data_from_non_main_thread_before_configuration_completed)
+{
+ test_thread_type = SThreadType::STHREAD_TYPE_PACKET;
+ appid_log_call_count = 0;
+
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value = "test_value";
+
+ bool added = test_user_data_map->add_user_data(table, key, value);
+ CHECK_FALSE(added);
+ CHECK_EQUAL(0, appid_log_call_count);
+
+ test_thread_type = SThreadType::STHREAD_TYPE_MAIN;
+}
+
+TEST(user_data_map_test, add_user_data_from_non_main_thread_after_configuration_completed)
+{
+ test_thread_type = SThreadType::STHREAD_TYPE_PACKET;
+ appid_log_call_count = 0;
+
+ const std::string table = "test_table";
+ const std::string key = "test_key";
+ const std::string value = "test_value";
+
+ test_user_data_map->set_configuration_completed(true);
+ bool added = test_user_data_map->add_user_data(table, key, value);
+ CHECK_FALSE(added);
+ CHECK_EQUAL(1, appid_log_call_count);
+
+ test_thread_type = SThreadType::STHREAD_TYPE_MAIN;
+}
+
+int main(int argc, char** argv)
+{
+ int rc = CommandLineTestRunner::RunAllTests(argc, argv);
+
+ return rc;
+}
#include "user_data_map.h"
+#include "main/thread.h"
+
+static THREAD_LOCAL bool configuration_completed;
+
UserDataMap::~UserDataMap()
{
user_data_maps.clear();
}
-void UserDataMap::add_user_data(const std::string& table, const std::string& key,
- const std::string& item)
+bool UserDataMap::add_user_data(const std::string &table, const std::string &key,
+ const std::string &item, bool override_existing)
{
- if (user_data_maps.find(table) != user_data_maps.end())
+
+ if (snort::get_thread_type() != SThreadType::STHREAD_TYPE_MAIN)
{
- if (user_data_maps[table].find(key) != user_data_maps[table].end())
- {
- APPID_LOG(nullptr, TRACE_WARNING_LEVEL,"ignoring duplicate key %s in table %s",
+ if (configuration_completed)
+ APPID_LOG(nullptr, TRACE_WARNING_LEVEL, "AppId: ignoring user data with key %s in table %s from non-main thread\n",
key.c_str(), table.c_str());
- return;
+ return false;
+ }
+
+ auto table_it = user_data_maps.find(table);
+ if (table_it != user_data_maps.end())
+ {
+ if (override_existing)
+ {
+ table_it->second[key] = item;
+ }
+ else
+ {
+ auto insert_result = table_it->second.try_emplace(key, item);
+ if (insert_result.second == false)
+ {
+ APPID_LOG(nullptr, TRACE_WARNING_LEVEL, "AppId: ignoring duplicate key %s in table %s\n",
+ key.c_str(), table.c_str());
+ return false;
+ }
}
- user_data_maps[table][key] = item;
}
else
{
user_map[key] = item;
user_data_maps[table] = user_map;
}
+
+ return true;
}
const char* UserDataMap::get_user_data_value_str(const std::string& table,
const std::string& key)
{
- if (user_data_maps.find(table) != user_data_maps.end() and
- user_data_maps[table].find(key) != user_data_maps[table].end())
+ auto table_it = user_data_maps.find(table);
+ if (table_it != user_data_maps.end())
{
- return user_data_maps[table][key].c_str();
+ auto key_it = table_it->second.find(key);
+ if (key_it != table_it->second.end())
+ {
+ return key_it->second.c_str();
+ }
}
- else
- return nullptr;
+
+ return nullptr;
+}
+
+void UserDataMap::set_configuration_completed(bool completed)
+{
+ configuration_completed = completed;
}