mirror of
https://github.com/openappsec/openappsec.git
synced 2025-11-16 01:12:18 +03:00
First release of open-appsec source code
This commit is contained in:
48
core/CMakeLists.txt
Normal file
48
core/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
add_subdirectory(cptest)
|
||||
add_subdirectory(agent_core_utilities)
|
||||
add_subdirectory(shell_cmd)
|
||||
add_subdirectory(debug_is)
|
||||
add_subdirectory(time_proxy)
|
||||
add_subdirectory(singleton)
|
||||
add_subdirectory(buffers)
|
||||
add_subdirectory(mainloop)
|
||||
add_subdirectory(environment)
|
||||
add_subdirectory(table)
|
||||
add_subdirectory(rest)
|
||||
add_subdirectory(report)
|
||||
add_subdirectory(logging)
|
||||
add_subdirectory(connkey)
|
||||
add_subdirectory(message)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(agent_details)
|
||||
add_subdirectory(event_is)
|
||||
add_subdirectory(encryptor)
|
||||
add_subdirectory(intelligence_is_v2)
|
||||
add_subdirectory(cpu)
|
||||
add_subdirectory(memory_consumption)
|
||||
add_subdirectory(shmem_ipc)
|
||||
add_subdirectory(shm_pkt_queue)
|
||||
add_subdirectory(instance_awareness)
|
||||
add_subdirectory(socket_is)
|
||||
add_subdirectory(agent_details_reporter)
|
||||
add_subdirectory(messaging_buffer)
|
||||
add_subdirectory(metric)
|
||||
add_subdirectory(version)
|
||||
add_subdirectory(tenant_manager)
|
||||
add_subdirectory(compression)
|
||||
add_subdirectory(attachments)
|
||||
|
||||
add_library(ngen_core SHARED ".")
|
||||
target_link_libraries(
|
||||
ngen_core
|
||||
-Wl,-whole-archive
|
||||
"table;debug_is;shell_cmd;metric;tenant_manager;message;encryptor;time_proxy;singleton;mainloop;environment;logging;report;rest"
|
||||
"config;intelligence_is_v2;event_is;memory_consumption;connkey"
|
||||
"instance_awareness;socket_is;messaging_buffer;agent_details;agent_details_reporter;buffers;cpu;agent_core_utilities"
|
||||
-Wl,-no-whole-archive
|
||||
)
|
||||
|
||||
add_subdirectory(core_ut)
|
||||
|
||||
install(TARGETS ngen_core DESTINATION lib)
|
||||
install(TARGETS ngen_core DESTINATION orchestration/lib)
|
||||
23
core/README.md
Normal file
23
core/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# `core`
|
||||
|
||||
## Overview
|
||||
The `core` directory contains code that is used to generate the libraries common to several elements in the system - most notibly the `ngen_core` library.
|
||||
The code in this directory should not be changed without informing the Infinity Next Agent group, as changes here have a far reaching consequences.
|
||||
|
||||
## Important code sections
|
||||
For the typical developer, the important parts of agent-core are all under the `include` directory, which in turn is divided into several sections:
|
||||
- `general` - This section covers headers files that impact both attachments and services, for example the communication between services and attachements, or the files related to unit-testing.
|
||||
- `services_sdk/interfaces` - This section contains the interfaces that are made available for components in services.
|
||||
- `services_sdk/interfaces/mock` - This section holds mock objects for the interfaces, to be used in unit-tests.
|
||||
- `services_sdk/resources` - This section contains capabilities available to components in services in forms other than interfaces (such as creating a log).
|
||||
- `services_sdk/utilities` - This section contains helper code that can be used to develop components faster - customized containers, etc..
|
||||
- `include/internal` - This section is meant for internal implementation of the libraries and is less relevant for the typical developer.
|
||||
- attachments - This section holds capabilities used by attachments, such as communicating with their services - so services also have access to them.
|
||||
|
||||
|
||||
## Important Notice
|
||||
The above mentioned `include` directories have there own sub-directies, which are not described in the `README` file. *Typically you should not include any of these files directly yourself!*
|
||||
These files serve one of two purposes:
|
||||
1. Supporting the higher-files - in which case these files will handle the required inclution themselves.
|
||||
2. Provide a "unsafe" interface - for example, interfaces that are very performance intensive.
|
||||
Either way, please consult the Infinity Next Agents group before including these files yourself.
|
||||
3
core/agent_core_utilities/CMakeLists.txt
Executable file
3
core/agent_core_utilities/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
add_library(agent_core_utilities agent_core_utilities.cc)
|
||||
|
||||
add_subdirectory(agent_core_utilities_ut)
|
||||
346
core/agent_core_utilities/agent_core_utilities.cc
Executable file
346
core/agent_core_utilities/agent_core_utilities.cc
Executable file
@@ -0,0 +1,346 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "agent_core_utilities.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
#include <sys/sendfile.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <sstream>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
|
||||
USE_DEBUG_FLAG(D_INFRA_UTILS);
|
||||
|
||||
namespace NGEN
|
||||
{
|
||||
|
||||
namespace Filesystem
|
||||
{
|
||||
|
||||
bool
|
||||
exists(const string &path)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS) << "Checking if path exists. Path: " << path;
|
||||
struct stat buffer;
|
||||
if (stat(path.c_str(), &buffer) == 0) {
|
||||
dbgTrace(D_INFRA_UTILS) << "Path already exists. Path: " << path;
|
||||
return true;
|
||||
}
|
||||
|
||||
dbgTrace(D_INFRA_UTILS) << "Path does not exists. Path: " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
makeDir(const string &path, mode_t permission)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS)
|
||||
<< "Trying to create directory. Path: "
|
||||
<< path
|
||||
<< ", permission: "
|
||||
<< to_string(permission);
|
||||
|
||||
if (mkdir(path.c_str(), permission) != 0) {
|
||||
int error = errno;
|
||||
dbgDebug(D_INFRA_UTILS) << "Failed to create directory. Path: " << path << ", Error: " << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
dbgTrace(D_INFRA_UTILS) << "Successfully created directory. Path: " << path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
makeDirRecursive(const string &path, mode_t permission)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS)
|
||||
<< "Trying to create directory. Path: "
|
||||
<< path
|
||||
<< ", permission: "
|
||||
<< to_string(permission);
|
||||
stringstream path_stream(path);
|
||||
const char path_delimiter = '/';
|
||||
string sub_path = (path.front() == path_delimiter ? "/" : "");
|
||||
string token;
|
||||
|
||||
while (getline(path_stream, token, path_delimiter)) {
|
||||
if (token == "") continue;
|
||||
sub_path += (token + path_delimiter);
|
||||
if (!exists(sub_path) && !makeDir(sub_path, permission)) {
|
||||
dbgDebug(D_INFRA_UTILS) << "Failed to create directory. Path: " << path;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dbgTrace(D_INFRA_UTILS) << "Successfully created directory. Path: " << path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
copyFile(const string &src, const string &dest, bool overide_if_exists, mode_t permission)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS)
|
||||
<< "Trying to copy file. Source: "
|
||||
<< src
|
||||
<< ", Destination: "
|
||||
<< dest
|
||||
<< ", Should override: "
|
||||
<< (overide_if_exists? "true" : "false")
|
||||
<< ", permission: "
|
||||
<< to_string(permission);
|
||||
if (!exists(src)) {
|
||||
dbgDebug(D_INFRA_UTILS) << "Failed to copy file. Error: source file does not exists";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (exists(dest) && !overide_if_exists) {
|
||||
dbgDebug(D_INFRA_UTILS) << "Failed to copy file. Error: destination file already exists";
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat stat_buf;
|
||||
int source_fd = open(src.c_str(), O_RDONLY);
|
||||
fstat(source_fd, &stat_buf);
|
||||
int dest_fd = open(dest.c_str(), O_WRONLY | O_CREAT, permission);
|
||||
int bytes_copied = 1;
|
||||
while (bytes_copied > 0) {
|
||||
static const int buf_size = 4096*1000;
|
||||
bytes_copied = sendfile(dest_fd, source_fd, 0, buf_size);
|
||||
}
|
||||
|
||||
dbgTrace(D_INFRA_UTILS) << "Finished attempt to copy file. Res: " << (bytes_copied != -1 ? "Success" : "Error");
|
||||
return bytes_copied != -1;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
deleteFile(const string &path)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS) << "Trying to delete file. Path: " << path;
|
||||
if (unlink(path.c_str()) != 0) {
|
||||
int error = errno;
|
||||
dbgDebug(D_INFRA_UTILS) << "Failed to delete file. Path: " << path << ", Error: " << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
dbgTrace(D_INFRA_UTILS) << "Successfully delete file. Path: " << path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
deleteDirectory(const string &path, bool delete_content)
|
||||
{
|
||||
dbgFlow(D_INFRA_UTILS)
|
||||
<< "Trying to delete directory. Path: "
|
||||
<< path
|
||||
<< ", Delete content: "
|
||||
<< (delete_content? "true" : "false");
|
||||
|
||||
struct dirent *entry = nullptr;
|
||||
DIR *directory = opendir(path.c_str());
|
||||
|
||||
if (directory == nullptr) {
|
||||
int orig_errno = errno;
|
||||
dbgWarning(D_INFRA_UTILS) << "Fail to open directory. Path: " << path << ", Errno: " << orig_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
while (delete_content && (entry = readdir(directory))) {
|
||||
string entry_file_name = entry->d_name;
|
||||
static const string curr_dir(".");
|
||||
static const string parent_dir("..");
|
||||
if (entry_file_name == curr_dir || entry_file_name == parent_dir) {
|
||||
dbgTrace(D_INFRA_UTILS) << "Skipping irrelevant directory entries. Entry name: " << entry_file_name;
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_file_name = path + (path.back() == '/' ? "" : "/") + entry_file_name;
|
||||
struct stat statbuf;
|
||||
if (!stat(entry_file_name.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
res &= deleteDirectory(entry_file_name, delete_content);
|
||||
} else {
|
||||
res &= deleteFile(entry_file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res &= (rmdir(path.c_str()) == 0);
|
||||
dbgTrace(D_INFRA_UTILS) << "Finished attempt to delete directory. Res: " << (res ? "Success" : "Error");
|
||||
return res;
|
||||
}
|
||||
|
||||
string
|
||||
convertToHumanReadable(uint64_t size_in_bytes)
|
||||
{
|
||||
stringstream res;
|
||||
if (size_in_bytes < 1000) {
|
||||
res << size_in_bytes << " Bytes";
|
||||
return res.str();
|
||||
}
|
||||
float size = size_in_bytes;
|
||||
size /= 1024;
|
||||
res << setprecision(2) << fixed;
|
||||
if (size < 1000) {
|
||||
res << size << " KB";
|
||||
return res.str();
|
||||
}
|
||||
size /= 1024;
|
||||
if (size < 1000) {
|
||||
res << size << " MB";
|
||||
return res.str();
|
||||
}
|
||||
size /= 1024;
|
||||
res << size << " GB";
|
||||
return res.str();
|
||||
}
|
||||
|
||||
}// namespace Filesystem
|
||||
|
||||
namespace Regex
|
||||
{
|
||||
|
||||
bool
|
||||
regexMatch(const char *file, int line, const char *sample, cmatch &match, const regex ®ex)
|
||||
{
|
||||
try {
|
||||
return regex_match(sample, match, regex);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_match @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
regexMatch(const char *file, int line, const string &sample, smatch &match, const regex ®ex)
|
||||
{
|
||||
try {
|
||||
return regex_match(sample, match, regex);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_match @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
regexMatch(const char *file, int line, const string &sample, const regex ®ex)
|
||||
{
|
||||
try {
|
||||
return regex_match(sample, regex);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_match @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
regexMatch(const char *file, int line, string &sample, const regex ®ex)
|
||||
{
|
||||
try {
|
||||
return regex_match(sample, regex);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_match @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
regexSearch(const char *file, int line, const string &sample, smatch &match, const regex ®ex)
|
||||
{
|
||||
try {
|
||||
return regex_search(sample, match, regex);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_search @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
regexReplace(const char *file, int line, const string &sample, const regex ®ex, const string &replace)
|
||||
{
|
||||
try {
|
||||
return regex_replace(sample, regex, replace);
|
||||
} catch (const runtime_error &err) {
|
||||
dbgError(D_INFRA_UTILS)
|
||||
<< "FAILURE during regex_replace @ "
|
||||
<< file
|
||||
<< ":"
|
||||
<< line
|
||||
<< "; sample='"
|
||||
<< sample << "', pattern='"
|
||||
<< regex.str()
|
||||
<< "', replace='"
|
||||
<< replace
|
||||
<< "': "
|
||||
<< err.what();
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Regex
|
||||
|
||||
} // namespace NGEN
|
||||
@@ -0,0 +1,7 @@
|
||||
include_directories(${CMAKE_SOURCE_DIR}/cptest/include)
|
||||
|
||||
add_unit_test(
|
||||
agent_core_utilities_ut
|
||||
"agent_core_utilities_ut.cc"
|
||||
"agent_core_utilities;shell_cmd;config;time_proxy;-lboost_regex"
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "agent_core_utilities.h"
|
||||
#include "shell_cmd.h"
|
||||
#include "cptest.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_environment.h"
|
||||
#include "config_component.h"
|
||||
#include "time_proxy.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
auto contexts = make_pair(std::vector<Context *>(), false);
|
||||
|
||||
class AgentCoreUtilUT : public Test
|
||||
{
|
||||
public:
|
||||
AgentCoreUtilUT()
|
||||
{
|
||||
ON_CALL(mock_env, getActiveContexts()).WillByDefault(ReturnPointee(&contexts));
|
||||
}
|
||||
|
||||
~AgentCoreUtilUT()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
NiceMock<MockEnvironment> mock_env;
|
||||
TimeProxyComponent time_get;
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
ConfigComponent config;
|
||||
chrono::milliseconds mocked_cur_time;
|
||||
};
|
||||
|
||||
TEST_F(AgentCoreUtilUT, filesTest)
|
||||
{
|
||||
EXPECT_FALSE(NGEN::Filesystem::exists("/i/am/not/a/real/path"));
|
||||
|
||||
const vector<string> lines{"i am a line in the text file", "i am iron man"};
|
||||
CPTestTempfile test_file(lines);
|
||||
ASSERT_TRUE(NGEN::Filesystem::exists(test_file.fname));
|
||||
|
||||
string output_orig = test_file.readFile();
|
||||
string new_path = test_file.fname + ".new";
|
||||
ASSERT_TRUE(NGEN::Filesystem::copyFile(test_file.fname, new_path, false));
|
||||
ASSERT_TRUE(NGEN::Filesystem::exists(new_path));
|
||||
ASSERT_FALSE(NGEN::Filesystem::copyFile(test_file.fname, new_path, false));
|
||||
ASSERT_TRUE(NGEN::Filesystem::copyFile(test_file.fname, new_path, true));
|
||||
string output_new;
|
||||
{
|
||||
ifstream new_file_stream(new_path);
|
||||
ASSERT_TRUE(new_file_stream.good());
|
||||
stringstream buffer;
|
||||
buffer << new_file_stream.rdbuf();
|
||||
output_new = buffer.str();
|
||||
}
|
||||
|
||||
EXPECT_EQ(output_orig, output_new);
|
||||
EXPECT_THAT(output_new, HasSubstr("i am a line in the text file"));
|
||||
EXPECT_THAT(output_new, HasSubstr("i am iron man"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::deleteFile(test_file.fname));
|
||||
EXPECT_TRUE(NGEN::Filesystem::deleteFile(new_path));
|
||||
EXPECT_FALSE(NGEN::Filesystem::exists(test_file.fname));
|
||||
EXPECT_FALSE(NGEN::Filesystem::exists(new_path));
|
||||
}
|
||||
|
||||
TEST_F(AgentCoreUtilUT, directoryTest)
|
||||
{
|
||||
EXPECT_FALSE(NGEN::Filesystem::exists("/tmp/1/2/3/4"));
|
||||
EXPECT_FALSE(NGEN::Filesystem::makeDir("/tmp/1/2/3/4"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::makeDir("/tmp/1"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::exists("/tmp/1"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::makeDirRecursive("/tmp/1/2/3/4"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::exists("/tmp/1/2/3/4"));
|
||||
EXPECT_FALSE(NGEN::Filesystem::deleteDirectory("/tmp/1"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::deleteDirectory("/tmp/1/2/3/4"));
|
||||
EXPECT_TRUE(NGEN::Filesystem::deleteDirectory("/tmp/1", true));
|
||||
EXPECT_FALSE(NGEN::Filesystem::exists("/tmp/1"));
|
||||
}
|
||||
TEST_F(AgentCoreUtilUT, printTest)
|
||||
{
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(0), "0 Bytes");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(20), "20 Bytes");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000), "0.98 KB");
|
||||
uint64_t kilobyte = 1024;
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(kilobyte), "1.00 KB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*kilobyte - 1), "1000.00 KB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*kilobyte), "0.98 MB");
|
||||
uint64_t megabyte = kilobyte * kilobyte;
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(megabyte), "1.00 MB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*megabyte - kilobyte), "1000.00 MB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*megabyte), "0.98 GB");
|
||||
uint64_t gigabyte = megabyte * kilobyte;
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(gigabyte), "1.00 GB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*gigabyte - megabyte), "1000.00 GB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1000*gigabyte), "1000.00 GB");
|
||||
EXPECT_EQ(NGEN::Filesystem::convertToHumanReadable(1024*gigabyte), "1024.00 GB");
|
||||
}
|
||||
3
core/agent_details/CMakeLists.txt
Executable file
3
core/agent_details/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
add_library(agent_details agent_details.cc)
|
||||
|
||||
add_subdirectory(agent_details_ut)
|
||||
307
core/agent_details/agent_details.cc
Executable file
307
core/agent_details/agent_details.cc
Executable file
@@ -0,0 +1,307 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "agent_details.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "sasal.h"
|
||||
|
||||
SASAL_START // Orchestration - Communication
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_ORCHESTRATOR);
|
||||
|
||||
const map<string, I_AgentDetails::MachineType> AgentDetails::machineTypes({
|
||||
{ "Amazon EC2", I_AgentDetails::MachineType::AWS },
|
||||
{ "Xen", I_AgentDetails::MachineType::AWS },
|
||||
{ "Microsoft Corporation", I_AgentDetails::MachineType::AZURE },
|
||||
{ "VMware, Inc.", I_AgentDetails::MachineType::ON_PREM }
|
||||
});
|
||||
|
||||
void
|
||||
AgentDetails::init()
|
||||
{
|
||||
registerMachineType();
|
||||
}
|
||||
|
||||
bool
|
||||
AgentDetails::readAgentDetails()
|
||||
{
|
||||
auto agent_details_path = getConfigurationWithDefault<string>(
|
||||
getFilesystemPathConfig() + "/conf/agent_details.json",
|
||||
"Agent details",
|
||||
"File path"
|
||||
);
|
||||
ifstream file(agent_details_path);
|
||||
if (!file.is_open()) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Agent details file does not exist. File: " << agent_details_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
stringstream file_stream;
|
||||
try {
|
||||
file_stream << file.rdbuf();
|
||||
cereal::JSONInputArchive archive_in(file_stream);
|
||||
serialize(archive_in);
|
||||
} catch (exception &e) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Failed to parse agent details."
|
||||
<< " File: " << agent_details_path
|
||||
<< ", Error: " << e.what();
|
||||
return false;
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AgentDetails::writeAgentDetails()
|
||||
{
|
||||
auto agent_details_path = getConfigurationWithDefault<string>(
|
||||
getFilesystemPathConfig() + "/conf/agent_details.json",
|
||||
"Agent details",
|
||||
"File path"
|
||||
);
|
||||
|
||||
try {
|
||||
ofstream ostream(agent_details_path);
|
||||
cereal::JSONOutputArchive archive_out(ostream);
|
||||
serialize(archive_out);
|
||||
} catch (exception &e) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Failed to write the agent details."
|
||||
<< " File: " << agent_details_path
|
||||
<< ", Error: " << e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetails::serialize(cereal::JSONOutputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("Fog domain", fog_domain));
|
||||
ar(cereal::make_nvp("Agent ID", agent_id));
|
||||
ar(cereal::make_nvp("Fog port", fog_port));
|
||||
ar(cereal::make_nvp("Tenant ID", tenant_id));
|
||||
ar(cereal::make_nvp("Profile ID", profile_id));
|
||||
ar(cereal::make_nvp("Encrypted connection", encrypted_connection));
|
||||
ar(cereal::make_nvp("OpenSSL certificates directory", openssl_dir));
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("Proxy", proxy));
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("Cluster ID", cluster_id));
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
|
||||
try {
|
||||
static const EnumArray<OrchestrationMode, std::string> orchestraiton_mode_str {
|
||||
"online_mode",
|
||||
"offline_mode",
|
||||
"hybrid_mode"
|
||||
};
|
||||
std::string orchestraiton_mode_string = orchestraiton_mode_str[orchestration_mode];
|
||||
ar(cereal::make_nvp("Orchestration mode", orchestraiton_mode_string));
|
||||
|
||||
bool is_offline_mode = (orchestration_mode == OrchestrationMode::OFFLINE);
|
||||
ar(cereal::make_nvp("Is Offline Mode", is_offline_mode));
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetails::serialize(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("Fog domain", fog_domain));
|
||||
ar(cereal::make_nvp("Agent ID", agent_id));
|
||||
ar(cereal::make_nvp("Fog port", fog_port));
|
||||
ar(cereal::make_nvp("Tenant ID", tenant_id));
|
||||
ar(cereal::make_nvp("Profile ID", profile_id));
|
||||
ar(cereal::make_nvp("Encrypted connection", encrypted_connection));
|
||||
ar(cereal::make_nvp("OpenSSL certificates directory", openssl_dir));
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("Proxy", proxy));
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
|
||||
try {
|
||||
ar(cereal::make_nvp("Cluster ID", cluster_id));
|
||||
if (!cluster_id.empty()) {
|
||||
Singleton::Consume<I_Environment>::by<AgentDetails>()->getConfigurationContext().registerValue<string>(
|
||||
"k8sClusterId",
|
||||
cluster_id,
|
||||
EnvKeyAttr::LogSection::SOURCE
|
||||
);
|
||||
}
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
|
||||
try {
|
||||
static const std::map<std::string, OrchestrationMode> orchestrationModeMap {
|
||||
{ "online_mode", OrchestrationMode::ONLINE },
|
||||
{ "offline_mode", OrchestrationMode::OFFLINE },
|
||||
{ "hybrid_mode", OrchestrationMode::HYBRID }
|
||||
};
|
||||
std::string orchestraiton_mode_string;
|
||||
ar(cereal::make_nvp("Orchestration mode", orchestraiton_mode_string));
|
||||
auto iter = orchestrationModeMap.find(orchestraiton_mode_string);
|
||||
if (iter != orchestrationModeMap.end()) {
|
||||
orchestration_mode = iter->second;
|
||||
}
|
||||
} catch (...) {
|
||||
try {
|
||||
bool is_offline_mode = false;
|
||||
ar(cereal::make_nvp("Is Offline Mode", is_offline_mode));
|
||||
if (is_offline_mode) {
|
||||
orchestration_mode = OrchestrationMode::OFFLINE;
|
||||
} else {
|
||||
orchestration_mode = OrchestrationMode::ONLINE;
|
||||
}
|
||||
} catch (...) {
|
||||
ar.setNextName(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
AgentDetails::getAgentId() const
|
||||
{
|
||||
static string default_agent_id = "Unknown";
|
||||
if (agent_id.empty()) return default_agent_id;
|
||||
return agent_id;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
AgentDetails::getProxy() const
|
||||
{
|
||||
if (proxy == "") return genError("Proxy not set");
|
||||
return proxy;
|
||||
}
|
||||
|
||||
Maybe<uint16_t>
|
||||
AgentDetails::getFogPort() const
|
||||
{
|
||||
if (fog_port == 0) return genError("Fog port is unset");
|
||||
return fog_port;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
AgentDetails::getFogDomain() const
|
||||
{
|
||||
if (fog_domain.empty()) return genError("Fog domain is unset");
|
||||
return fog_domain;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetails::setClusterId(const std::string &_cluster_id)
|
||||
{
|
||||
dbgTrace(D_ORCHESTRATOR) << "Setting Cluster Id in the agent details. Cluster ID: " << _cluster_id;
|
||||
cluster_id = _cluster_id;
|
||||
writeAgentDetails();
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetails::preload()
|
||||
{
|
||||
registerExpectedConfiguration<string>("orchestration", "Agent details path");
|
||||
registerConfigLoadCb([this] () { readAgentDetails(); });
|
||||
}
|
||||
|
||||
string
|
||||
AgentDetails::getTenantId() const
|
||||
{
|
||||
return tenant_id;
|
||||
}
|
||||
|
||||
string
|
||||
AgentDetails::getProfileId() const
|
||||
{
|
||||
return profile_id;
|
||||
}
|
||||
|
||||
string
|
||||
AgentDetails::getClusterId() const
|
||||
{
|
||||
return cluster_id;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
AgentDetails::getOpenSSLDir() const
|
||||
{
|
||||
if (openssl_dir.empty()) return genError("OpenSSL certificates directory was not set");
|
||||
return openssl_dir;
|
||||
}
|
||||
|
||||
OrchestrationMode
|
||||
AgentDetails::getOrchestrationMode() const
|
||||
{
|
||||
return orchestration_mode;
|
||||
}
|
||||
|
||||
Maybe<I_AgentDetails::MachineType>
|
||||
AgentDetails::getMachineTypeFromDmiTable()
|
||||
{
|
||||
static const string decode_machine_type_cmd = "dmidecode -s system-manufacturer | tr -d '\\n'";
|
||||
|
||||
I_ShellCmd *i_shell_cmd = Singleton::Consume<I_ShellCmd>::by<AgentDetails>();
|
||||
auto machine_type = i_shell_cmd->getExecOutput(decode_machine_type_cmd);
|
||||
if (!machine_type.ok()) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Error. Could not decode the DMI table. " << machine_type.getErr();
|
||||
return I_AgentDetails::MachineType::UNRECOGNIZED;
|
||||
} else if (machine_type.unpack().empty()) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Error. Could not decode the DMI table. Table value is empty";
|
||||
return I_AgentDetails::MachineType::UNRECOGNIZED;
|
||||
}
|
||||
|
||||
dbgInfo(D_ORCHESTRATOR) << "Decoded the DMI talble: " << machine_type.unpack();
|
||||
auto resolved_machine_type = machineTypes.find(*machine_type);
|
||||
if (resolved_machine_type == end(machineTypes)) return I_AgentDetails::MachineType::UNRECOGNIZED;
|
||||
|
||||
return resolved_machine_type->second;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetails::registerMachineType()
|
||||
{
|
||||
Maybe<I_AgentDetails::MachineType> machine_type = getMachineTypeFromDmiTable();
|
||||
if (!machine_type.ok()) {
|
||||
dbgWarning(D_ORCHESTRATOR)
|
||||
<< "Error. Could not get machine type from the DMI table. "
|
||||
<< machine_type.getErr();
|
||||
return;
|
||||
}
|
||||
if (machine_type.unpack() == I_AgentDetails::MachineType::UNRECOGNIZED) {
|
||||
dbgWarning(D_ORCHESTRATOR) << "Error. Machine type is unrecognized";
|
||||
}
|
||||
Singleton::Consume<I_Environment>::by<AgentDetails>()->registerValue<I_AgentDetails::MachineType>(
|
||||
"MachineType", machine_type.unpack()
|
||||
);
|
||||
dbgInfo(D_ORCHESTRATOR) << "Setting machine type " << static_cast<int>(machine_type.unpack());
|
||||
}
|
||||
|
||||
SASAL_END
|
||||
9
core/agent_details/agent_details_ut/CMakeLists.txt
Normal file
9
core/agent_details/agent_details_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
include_directories(${CMAKE_SOURCE_DIR}/components/include)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/cptest/include)
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
agent_details_ut
|
||||
"agent_details_ut.cc"
|
||||
"singleton;config;agent_details;environment;metric;event_is;-lboost_regex"
|
||||
)
|
||||
156
core/agent_details/agent_details_ut/agent_details_ut.cc
Normal file
156
core/agent_details/agent_details_ut/agent_details_ut.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "agent_details.h"
|
||||
|
||||
#include "mock/mock_encryptor.h"
|
||||
#include "mock/mock_shell_cmd.h"
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "buffer.h"
|
||||
#include "cptest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class AgentDetailsTest : public Test
|
||||
{
|
||||
public:
|
||||
AgentDetailsTest()
|
||||
{
|
||||
config = Singleton::Consume<Config::I_Config>::from(conf);
|
||||
}
|
||||
|
||||
::Environment env;
|
||||
ConfigComponent conf;
|
||||
StrictMock<MockEncryptor> mock_encryptor;
|
||||
StrictMock<MockShellCmd> mock_shell_cmd;
|
||||
Config::I_Config *config = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(AgentDetailsTest, doNothing)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(AgentDetailsTest, basicTest)
|
||||
{
|
||||
const vector<string> agent_details_vec {
|
||||
"{",
|
||||
" \"Fog domain\": \"fog.com\",",
|
||||
" \"Agent ID\": \"fdfdf-5454-dfd\",",
|
||||
" \"Fog port\": 443,",
|
||||
" \"Encrypted connection\": false,",
|
||||
" \"Orchestration mode\": \"offline_mode\",",
|
||||
" \"Tenant ID\": \"tenant_id\",",
|
||||
" \"Profile ID\": \"profile\",",
|
||||
" \"Proxy\": \"http://proxy.checkpoint.com/\",",
|
||||
" \"OpenSSL certificates directory\": \"\"",
|
||||
"}"
|
||||
};
|
||||
AgentDetails agent_details;
|
||||
env.preload();
|
||||
agent_details.preload();
|
||||
EXPECT_CALL(
|
||||
mock_shell_cmd,
|
||||
getExecOutput("dmidecode -s system-manufacturer | tr -d '\\n'", _, _)
|
||||
).WillOnce(Return(string("Microsoft Corporation")));
|
||||
env.init();
|
||||
agent_details.init();
|
||||
|
||||
auto i_conf = Singleton::Consume<Config::I_Config>::from(conf);
|
||||
i_conf->reloadConfiguration();
|
||||
|
||||
CPTestTempfile agent_details_file(agent_details_vec);
|
||||
setConfiguration(agent_details_file.fname, "Agent details", "File path");
|
||||
|
||||
EXPECT_TRUE(agent_details.readAgentDetails());
|
||||
EXPECT_EQ(agent_details.getFogDomain().unpack(), "fog.com");
|
||||
EXPECT_EQ(agent_details.getFogPort().unpack(), 443);
|
||||
EXPECT_EQ(agent_details.getAgentId(), "fdfdf-5454-dfd");
|
||||
EXPECT_FALSE(agent_details.getSSLFlag());
|
||||
|
||||
agent_details.setSSLFlag(true);
|
||||
agent_details.setFogPort(80);
|
||||
agent_details.setFogDomain("fog.checkpoint.com");
|
||||
agent_details.setAgentId("dfdfdf-dfd");
|
||||
agent_details.setClusterId("d5bd7949-554e-4fac-86c3-6e4e5d46a034");
|
||||
EXPECT_EQ(agent_details.getFogDomain().unpack(), "fog.checkpoint.com");
|
||||
EXPECT_EQ(agent_details.getFogPort().unpack(), 80);
|
||||
EXPECT_EQ(agent_details.getAgentId(), "dfdfdf-dfd");
|
||||
EXPECT_EQ(agent_details.getTenantId(), "tenant_id");
|
||||
EXPECT_EQ(agent_details.getProfileId(), "profile");
|
||||
EXPECT_EQ(agent_details.getClusterId(), "d5bd7949-554e-4fac-86c3-6e4e5d46a034");
|
||||
|
||||
EXPECT_TRUE(agent_details.writeAgentDetails());
|
||||
|
||||
EXPECT_TRUE(agent_details.readAgentDetails());
|
||||
EXPECT_EQ(agent_details.getFogDomain().unpack(), "fog.checkpoint.com");
|
||||
EXPECT_EQ(agent_details.getFogPort().unpack(), 80);
|
||||
EXPECT_EQ(agent_details.getAgentId(), "dfdfdf-dfd");
|
||||
EXPECT_EQ(agent_details.getClusterId(), "d5bd7949-554e-4fac-86c3-6e4e5d46a034");
|
||||
EXPECT_TRUE(agent_details.getSSLFlag());
|
||||
EXPECT_THAT(agent_details.getProxy(), IsValue("http://proxy.checkpoint.com/"));
|
||||
agent_details.setProxy("none");
|
||||
EXPECT_THAT(agent_details.getProxy(), IsValue("none"));
|
||||
|
||||
EXPECT_TRUE(agent_details.getOrchestrationMode() == OrchestrationMode::OFFLINE);
|
||||
agent_details.setOrchestrationMode(OrchestrationMode::ONLINE);
|
||||
EXPECT_TRUE(agent_details.getOrchestrationMode() == OrchestrationMode::ONLINE);
|
||||
auto machine_type = Singleton::Consume<I_Environment>::from(env)->get<I_AgentDetails::MachineType>("MachineType");
|
||||
EXPECT_EQ(machine_type.unpack(), I_AgentDetails::MachineType::AZURE);
|
||||
}
|
||||
|
||||
TEST_F(AgentDetailsTest, openSSL)
|
||||
{
|
||||
const vector<string> agent_details_vec {
|
||||
"{",
|
||||
" \"Fog domain\": \"fog.com\",",
|
||||
" \"Agent ID\": \"fdfdf-5454-dfd\",",
|
||||
" \"Fog port\": 443,",
|
||||
" \"Encrypted connection\": false,",
|
||||
" \"Tenant ID\": \"tenant_id\",",
|
||||
" \"Profile ID\": \"profile\",",
|
||||
" \"OpenSSL certificates directory\": \"\"",
|
||||
"}"
|
||||
};
|
||||
|
||||
AgentDetails agent_details;
|
||||
agent_details.preload();
|
||||
|
||||
CPTestTempfile agent_details_file(agent_details_vec);
|
||||
setConfiguration(agent_details_file.fname, "Agent details", "File path");
|
||||
|
||||
EXPECT_FALSE(agent_details.getSSLFlag());
|
||||
EXPECT_THAT(agent_details.getOpenSSLDir(), IsError("OpenSSL certificates directory was not set"));
|
||||
|
||||
agent_details.setOpenSSLDir("a/b/c");
|
||||
EXPECT_THAT(agent_details.getOpenSSLDir(), IsValue("a/b/c"));
|
||||
|
||||
agent_details.setFogPort(10);
|
||||
agent_details.setSSLFlag(false);
|
||||
agent_details.setFogDomain("www.fog.checkpoint.com");
|
||||
agent_details.setOpenSSLDir("");
|
||||
|
||||
EXPECT_THAT(agent_details.getFogPort(), IsValue(10));
|
||||
EXPECT_FALSE(agent_details.getSSLFlag());
|
||||
EXPECT_THAT(agent_details.getFogDomain(), IsValue("www.fog.checkpoint.com"));
|
||||
EXPECT_THAT(agent_details.getOpenSSLDir(), IsError("OpenSSL certificates directory was not set"));
|
||||
|
||||
EXPECT_FALSE(agent_details.getOrchestrationMode() == OrchestrationMode::OFFLINE);
|
||||
agent_details.setOrchestrationMode(OrchestrationMode::OFFLINE);
|
||||
EXPECT_TRUE(agent_details.getOrchestrationMode() == OrchestrationMode::OFFLINE);
|
||||
}
|
||||
|
||||
TEST_F(AgentDetailsTest, unrecognizedMachineType)
|
||||
{
|
||||
env.preload();
|
||||
env.init();
|
||||
AgentDetails agent_details;
|
||||
EXPECT_CALL(
|
||||
mock_shell_cmd,
|
||||
getExecOutput("dmidecode -s system-manufacturer | tr -d '\\n'", _, _)
|
||||
).WillOnce(Return(string("Skynet")));
|
||||
agent_details.preload();
|
||||
agent_details.init();
|
||||
|
||||
auto machine_type = Singleton::Consume<I_Environment>::from(env)->get<I_AgentDetails::MachineType>("MachineType");
|
||||
EXPECT_EQ(machine_type.unpack(), I_AgentDetails::MachineType::UNRECOGNIZED);
|
||||
}
|
||||
3
core/agent_details_reporter/CMakeLists.txt
Normal file
3
core/agent_details_reporter/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(agent_details_reporter agent_details_reporter.cc agent_details_report.cc)
|
||||
|
||||
add_subdirectory(agent_details_reporter_ut)
|
||||
60
core/agent_details_reporter/agent_details_report.cc
Normal file
60
core/agent_details_reporter/agent_details_report.cc
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "agent_details_report.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
AgentDataReport::~AgentDataReport()
|
||||
{
|
||||
Singleton::Consume<I_AgentDetailsReporter>::by<AgentDataReport>()->sendReport(
|
||||
agent_details,
|
||||
policy_version,
|
||||
platform,
|
||||
architecture,
|
||||
agent_version
|
||||
);
|
||||
}
|
||||
|
||||
AgentDataReport &
|
||||
AgentDataReport::operator<<(const pair<string, string> &data)
|
||||
{
|
||||
agent_details << data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDataReport::setPolicyVersion(const string &_policy_version)
|
||||
{
|
||||
policy_version = _policy_version;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDataReport::setPlatform(const string &_platform)
|
||||
{
|
||||
platform = _platform;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDataReport::setArchitecture(const string &_architecture)
|
||||
{
|
||||
architecture = _architecture;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDataReport::setAgentVersion(const string &_agent_version)
|
||||
{
|
||||
agent_version = _agent_version;
|
||||
}
|
||||
399
core/agent_details_reporter/agent_details_reporter.cc
Normal file
399
core/agent_details_reporter/agent_details_reporter.cc
Normal file
@@ -0,0 +1,399 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "agent_details_reporter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "config.h"
|
||||
#include "rest.h"
|
||||
#include "rest_server.h"
|
||||
#include "agent_details_report.h"
|
||||
#include "customized_cereal_map.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_AGENT_DETAILS);
|
||||
|
||||
class AttributesListener : public ServerRest
|
||||
{
|
||||
public:
|
||||
void
|
||||
doCall() override
|
||||
{
|
||||
auto details_reporter = Singleton::Consume<I_AgentDetailsReporter>::from<AgentDetailsReporter>();
|
||||
bool is_override_allowed = allow_override.isActive() ? allow_override.get() : false;
|
||||
res = details_reporter->addAttr(attributes.get(), is_override_allowed);
|
||||
}
|
||||
|
||||
using attr_type = map<string, string>;
|
||||
C2S_PARAM(attr_type, attributes);
|
||||
C2S_OPTIONAL_PARAM(bool, allow_override);
|
||||
S2C_PARAM(bool, res);
|
||||
};
|
||||
|
||||
class AttributesSender : public ClientRest
|
||||
{
|
||||
public:
|
||||
AttributesSender(const map<string, string> &attr) : attributes(attr) {}
|
||||
|
||||
using attr_type = map<string, string>;
|
||||
C2S_PARAM(attr_type, attributes);
|
||||
S2C_OPTIONAL_PARAM(bool, res);
|
||||
};
|
||||
|
||||
class AgentDetailsReporter::Impl
|
||||
:
|
||||
Singleton::Provide<I_AgentDetailsReporter>::From<AgentDetailsReporter>
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
void fini();
|
||||
|
||||
void sendReport(
|
||||
const metaDataReport &meta_data,
|
||||
const Maybe<string> &policy_version,
|
||||
const Maybe<string> &platform,
|
||||
const Maybe<string> &architecture,
|
||||
const Maybe<string> &agent_version
|
||||
) override;
|
||||
|
||||
bool addAttr(const string &key, const string &val, bool allow_override = false) override;
|
||||
bool addAttr(const map<string, string> &attr, bool allow_override = false) override;
|
||||
void deleteAttr(const string &key) override;
|
||||
|
||||
bool sendAttributes() override;
|
||||
|
||||
private:
|
||||
template <typename Fstream, typename Archive>
|
||||
class AttrSerializer
|
||||
{
|
||||
public:
|
||||
AttrSerializer(map<string, string> &attr, const string &op) : attributes(attr), operation(op) {}
|
||||
~AttrSerializer() { handleAttrPersistence(); }
|
||||
|
||||
private:
|
||||
void
|
||||
handleAttrPersistence()
|
||||
{
|
||||
dbgFlow(D_AGENT_DETAILS);
|
||||
string Persistence_file_path = getConfigurationWithDefault<string>(
|
||||
getFilesystemPathConfig() + "/conf/reportedAttrBackup.json",
|
||||
"Agent details",
|
||||
"Attributes persistence file path"
|
||||
);
|
||||
dbgTrace(D_AGENT_DETAILS) << "Persistence file path: " << Persistence_file_path;
|
||||
|
||||
Fstream backup_file(Persistence_file_path);
|
||||
if (!backup_file.is_open()) {
|
||||
dbgWarning(D_AGENT_DETAILS)
|
||||
<< "Failed to open attributes Persistence file. Operation: "
|
||||
<< operation
|
||||
<< ", Path "
|
||||
<< Persistence_file_path;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Archive ar(backup_file);
|
||||
ar(cereal::make_nvp("attributes", attributes));
|
||||
} catch (const exception &e) {
|
||||
dbgWarning(D_AGENT_DETAILS)
|
||||
<< "Failed to serialize attributes. Operation: "
|
||||
<< operation
|
||||
<< ", Error: "
|
||||
<< e.what();
|
||||
return;
|
||||
}
|
||||
|
||||
dbgInfo(D_AGENT_DETAILS)
|
||||
<< "Successfully handled attributes persistence. Operation: "
|
||||
<< operation
|
||||
<< ", Path "
|
||||
<< Persistence_file_path;
|
||||
}
|
||||
|
||||
map<string, string> &attributes;
|
||||
const string &operation;
|
||||
};
|
||||
|
||||
map<string, string> new_attributes;
|
||||
map<string, string> attributes;
|
||||
|
||||
I_Messaging *messaging = nullptr;
|
||||
bool is_server;
|
||||
};
|
||||
|
||||
metaDataReport &
|
||||
metaDataReport::operator<<(const pair<string, string> &data)
|
||||
{
|
||||
agent_details.insert(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
metaDataReport::serialize(cereal::JSONOutputArchive &out_ar) const
|
||||
{
|
||||
for (auto &data : agent_details) {
|
||||
out_ar(cereal::make_nvp(data.first, data.second));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AgentDetailsReporter::Impl::addAttr(const string &key, const string &val, bool allow_override)
|
||||
{
|
||||
dbgDebug(D_AGENT_DETAILS)
|
||||
<< "Trying to add new attribute. Key: "
|
||||
<< key
|
||||
<< ", Value: "
|
||||
<< val
|
||||
<< " Should allow override: "
|
||||
<< (allow_override ? "true" : "false");
|
||||
|
||||
if (!allow_override) {
|
||||
if (attributes.count(key) > 0 || new_attributes.count(key) > 0) {
|
||||
dbgWarning(D_AGENT_DETAILS)
|
||||
<< "Cannot override an existing value with a new one. Existing Value: "
|
||||
<< (attributes.count(key) > 0 ? attributes[key] : new_attributes[key]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
new_attributes[key] = val;
|
||||
dbgDebug(D_AGENT_DETAILS) << "Successfully added new attribute";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
AgentDetailsReporter::Impl::addAttr(const map<string, string> &attr, bool allow_override)
|
||||
{
|
||||
dbgFlow(D_AGENT_DETAILS);
|
||||
bool ret = true;
|
||||
for (const pair<string, string> &single_attr : attr) {
|
||||
if (!addAttr(single_attr.first, single_attr.second, allow_override)) ret = false;
|
||||
}
|
||||
|
||||
dbgDebug(D_AGENT_DETAILS) << "Finished adding of new attributes map. Res: " << (ret ? "Success" : "Failure");
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetailsReporter::Impl::deleteAttr(const string &key)
|
||||
{
|
||||
dbgDebug(D_AGENT_DETAILS) << "Deleting existing attributes. Key: " << key;
|
||||
attributes.erase(key);
|
||||
new_attributes.erase(key);
|
||||
}
|
||||
|
||||
bool
|
||||
AgentDetailsReporter::Impl::sendAttributes()
|
||||
{
|
||||
dbgDebug(D_AGENT_DETAILS) << "Trying to send attributes";
|
||||
|
||||
if (new_attributes.empty()) {
|
||||
dbgDebug(D_AGENT_DETAILS) << "Skipping current attempt since no new attributes were added";
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const pair<string, string> &new_attr : new_attributes) {
|
||||
attributes[new_attr.first] = new_attr.second;
|
||||
}
|
||||
|
||||
|
||||
AttributesSender attr_to_send(attributes);
|
||||
if (is_server) {
|
||||
AttrSerializer<ofstream, cereal::JSONOutputArchive>(attributes, "save");
|
||||
|
||||
messaging->sendObjectWithPersistence(attr_to_send, I_Messaging::Method::PATCH, "/agents");
|
||||
dbgDebug(D_AGENT_DETAILS) << "Triggered persistent message request with attributes to the Fog";
|
||||
new_attributes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint retry = 3; retry > 0; retry--) {
|
||||
::Flags<MessageConnConfig> conn_flags;
|
||||
conn_flags.setFlag(MessageConnConfig::ONE_TIME_CONN);
|
||||
bool is_success = messaging->sendObject(
|
||||
attr_to_send,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7777, // primary Orchestrator's port
|
||||
conn_flags,
|
||||
"add-agent-details-attr"
|
||||
);
|
||||
if (!is_success) {
|
||||
is_success = messaging->sendObject(
|
||||
attr_to_send,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7778, // secondary Orchestrator's port
|
||||
conn_flags,
|
||||
"add-agent-details-attr"
|
||||
);
|
||||
}
|
||||
|
||||
if (is_success) {
|
||||
dbgDebug(D_AGENT_DETAILS) << "Successfully sent attributes to the Orchestrator";
|
||||
new_attributes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
dbgDebug(D_AGENT_DETAILS) << "Could not send attributes to the Orchestrator. Retries left: " << (retry - 1);
|
||||
Singleton::Consume<I_MainLoop>::by<AgentDetailsReporter>()->yield(chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
dbgWarning(D_AGENT_DETAILS) << "Completely failed to send attributes to the Orchestrator";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class additionalMetaDataRest : public ClientRest
|
||||
{
|
||||
public:
|
||||
additionalMetaDataRest(const metaDataReport &_additionalMetaData)
|
||||
:
|
||||
additionalMetaData(_additionalMetaData)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
setPolicyVersion(const string &policy_version)
|
||||
{
|
||||
policyVersion = policy_version;
|
||||
}
|
||||
|
||||
void
|
||||
setPlatform(const string &_platform)
|
||||
{
|
||||
platform = _platform;
|
||||
}
|
||||
|
||||
void
|
||||
setArchitecture(const string &_architecture)
|
||||
{
|
||||
architecture = _architecture;
|
||||
}
|
||||
|
||||
void
|
||||
setAgentVersion(const string &agent_version)
|
||||
{
|
||||
agentVersion = agent_version;
|
||||
}
|
||||
|
||||
void
|
||||
setAdditionalAttributes(const map<string, string> &attr)
|
||||
{
|
||||
attributes = attr;
|
||||
}
|
||||
|
||||
private:
|
||||
C2S_PARAM(metaDataReport, additionalMetaData);
|
||||
C2S_OPTIONAL_PARAM(string, agentVersion);
|
||||
C2S_OPTIONAL_PARAM(string, policyVersion);
|
||||
C2S_OPTIONAL_PARAM(string, platform);
|
||||
C2S_OPTIONAL_PARAM(string, architecture);
|
||||
|
||||
using attr_type = map<string, string>;
|
||||
C2S_OPTIONAL_PARAM(attr_type, attributes);
|
||||
};
|
||||
|
||||
void
|
||||
AgentDetailsReporter::Impl::init()
|
||||
{
|
||||
messaging = Singleton::Consume<I_Messaging>::by<AgentDetailsReporter>();
|
||||
auto is_orchestrator = Singleton::Consume<I_Environment>::by<AgentDetailsReporter>()->get<bool>("Is Orchestrator");
|
||||
is_server = is_orchestrator.ok() && *is_orchestrator;
|
||||
|
||||
if (is_server) {
|
||||
I_RestApi *rest = Singleton::Consume<I_RestApi>::by<AgentDetailsReporter>();
|
||||
rest->addRestCall<AttributesListener>(RestAction::ADD, "agent-details-attr");
|
||||
AttrSerializer<ifstream, cereal::JSONInputArchive>(new_attributes, "load");
|
||||
}
|
||||
|
||||
Singleton::Consume<I_MainLoop>::by<AgentDetailsReporter>()->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
chrono::seconds(30),
|
||||
[this]()
|
||||
{
|
||||
if (!sendAttributes()) {
|
||||
dbgWarning(D_AGENT_DETAILS) << "Failed to send periodic agent details attributes map";
|
||||
} else {
|
||||
dbgDebug(D_AGENT_DETAILS) << "Successfully sent periodic agent details attributes map";
|
||||
};
|
||||
},
|
||||
"Report agent details attributes",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetailsReporter::Impl::fini()
|
||||
{
|
||||
if (!new_attributes.empty()) {
|
||||
for (const pair<string, string> &new_attr : new_attributes) {
|
||||
attributes[new_attr.first] = new_attr.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_server) {
|
||||
AttrSerializer<ofstream, cereal::JSONOutputArchive>(attributes, "save");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AgentDetailsReporter::Impl::sendReport(
|
||||
const metaDataReport &meta_data,
|
||||
const Maybe<string> &policy_version,
|
||||
const Maybe<string> &platform,
|
||||
const Maybe<string> &architecture,
|
||||
const Maybe<string> &agent_version)
|
||||
{
|
||||
if (!is_server) return;
|
||||
|
||||
additionalMetaDataRest additional_metadata(meta_data);
|
||||
|
||||
if (policy_version.ok()) additional_metadata.setPolicyVersion(*policy_version);
|
||||
if (platform.ok()) additional_metadata.setPlatform(*platform);
|
||||
if (architecture.ok()) additional_metadata.setArchitecture(*architecture);
|
||||
if (agent_version.ok()) additional_metadata.setAgentVersion(*agent_version);
|
||||
|
||||
if (!new_attributes.empty()) {
|
||||
for (const pair<string, string> &new_attr : new_attributes) {
|
||||
attributes[new_attr.first] = new_attr.second;
|
||||
}
|
||||
AttrSerializer<ofstream, cereal::JSONOutputArchive>(attributes, "save");
|
||||
new_attributes.clear();
|
||||
additional_metadata.setAdditionalAttributes(attributes);
|
||||
}
|
||||
|
||||
messaging->sendObjectWithPersistence(additional_metadata, I_Messaging::Method::PATCH, "/agents");
|
||||
}
|
||||
|
||||
AgentDetailsReporter::AgentDetailsReporter()
|
||||
:
|
||||
Component("AgentDetailsReporter"),
|
||||
pimpl(make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
AgentDetailsReporter::~AgentDetailsReporter() {}
|
||||
|
||||
void AgentDetailsReporter::init() { pimpl->init(); }
|
||||
void AgentDetailsReporter::fini() { pimpl->fini(); }
|
||||
|
||||
void
|
||||
AgentDetailsReporter::preload()
|
||||
{
|
||||
registerExpectedConfiguration<string>("Agent details", "Attributes persistence file path");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
agent_details_reporter_ut
|
||||
"agent_details_reporter_ut.cc"
|
||||
"environment;config;rest;event_is;metric;agent_details_reporter;-lboost_regex"
|
||||
)
|
||||
@@ -0,0 +1,456 @@
|
||||
#include "agent_details_reporter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_messaging.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_rest_api.h"
|
||||
#include "environment.h"
|
||||
#include "agent_details_report.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class AgentReporterTest : public Test
|
||||
{
|
||||
public:
|
||||
AgentReporterTest()
|
||||
{
|
||||
env.preload();
|
||||
context.registerFunc<bool>("Is Orchestrator", [this](){ return is_server_mode; });
|
||||
context.activate();
|
||||
EXPECT_CALL(
|
||||
mock_mainloop,
|
||||
addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
chrono::microseconds(chrono::seconds(30)),
|
||||
_,
|
||||
"Report agent details attributes",
|
||||
false
|
||||
)
|
||||
).WillOnce(DoAll(SaveArg<2>(&periodic_report), Return(1)));
|
||||
|
||||
agent_details_reporter_comp.preload();
|
||||
|
||||
string config_json =
|
||||
"{\n"
|
||||
" \"Agent details\": {\n"
|
||||
" \"Attributes persistence file path\": [\n"
|
||||
" {\n"
|
||||
" \"value\": \"" + persistence_attr_file.fname + "\"\n"
|
||||
" }\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
istringstream ss(config_json);
|
||||
Singleton::Consume<Config::I_Config>::from(config_comp)->loadConfiguration(ss);
|
||||
|
||||
EXPECT_CALL(mock_rest, mockRestCall(RestAction::ADD, "agent-details-attr", _)).WillOnce(
|
||||
WithArg<2>(Invoke(this, &AgentReporterTest::saveRestServerCB))
|
||||
);
|
||||
|
||||
agent_details_reporter_comp.init();
|
||||
report = Singleton::Consume<I_AgentDetailsReporter>::from(agent_details_reporter_comp);
|
||||
}
|
||||
|
||||
~AgentReporterTest()
|
||||
{
|
||||
context.deactivate();
|
||||
}
|
||||
|
||||
bool
|
||||
saveRestServerCB(const unique_ptr<RestInit> &p)
|
||||
{
|
||||
add_details_rest_cb = p->getRest();
|
||||
return true;
|
||||
}
|
||||
|
||||
::Environment env;
|
||||
StrictMock<MockMainLoop> mock_mainloop;
|
||||
StrictMock<MockMessaging> mock_messaging;
|
||||
StrictMock<MockRestApi> mock_rest;
|
||||
I_MainLoop::Routine periodic_report;
|
||||
I_AgentDetailsReporter *report;
|
||||
CPTestTempfile persistence_attr_file;
|
||||
Context context;
|
||||
bool is_server_mode = true;
|
||||
ConfigComponent config_comp;
|
||||
AgentDetailsReporter agent_details_reporter_comp;
|
||||
unique_ptr<ServerRest> add_details_rest_cb;
|
||||
};
|
||||
|
||||
TEST_F(AgentReporterTest, dataReport)
|
||||
{
|
||||
string custom_data = "Linux version 24.00.15F";
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {\n"
|
||||
" \"custom_data\": \"Linux version 24.00.15F\"\n"
|
||||
" }"
|
||||
"\n}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
AgentDataReport() << AgentReportField(custom_data);;
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, labeledDataReport)
|
||||
{
|
||||
string data = "Linux version 24.00.15F";
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {\n"
|
||||
" \"this_is_custom_label\": \"Linux version 24.00.15F\"\n"
|
||||
" }"
|
||||
"\n}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
AgentDataReport() << AgentReportFieldWithLabel("this_is_custom_label", data);
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, multiDataReport)
|
||||
{
|
||||
string custom_data = "Linux version 24.00.15F";
|
||||
string data_to_report = "Agent Version 95.95.95.00A";
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {\n"
|
||||
" \"custom_data\": \"Linux version 24.00.15F\",\n"
|
||||
" \"this_is_custom_label\": \"Agent Version 95.95.95.00A\"\n"
|
||||
" }"
|
||||
"\n}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
AgentDataReport()
|
||||
<< AgentReportField(custom_data)
|
||||
<< AgentReportFieldWithLabel("this_is_custom_label", data_to_report);
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, multiDataReportWithRegistrationData)
|
||||
{
|
||||
string custom_data = "Linux version 24.00.15F";
|
||||
string data_to_report = "Agent Version 95.95.95.00A";
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {\n"
|
||||
" \"custom_data\": \"Linux version 24.00.15F\",\n"
|
||||
" \"this_is_custom_label\": \"Agent Version 95.95.95.00A\"\n"
|
||||
" },\n"
|
||||
" \"agentVersion\": \"1.15.9\",\n"
|
||||
" \"policyVersion\": \"ccc\",\n"
|
||||
" \"platform\": \"bbb\",\n"
|
||||
" \"architecture\": \"aaa\"\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
AgentDataReport agent_data;
|
||||
agent_data
|
||||
<< AgentReportField(custom_data)
|
||||
<< AgentReportFieldWithLabel("this_is_custom_label", data_to_report);
|
||||
|
||||
agent_data.setPolicyVersion("ccc");
|
||||
agent_data.setPlatform("bbb");
|
||||
agent_data.setArchitecture("aaa");
|
||||
agent_data.setAgentVersion("1.15.9");
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, basicAttrTest)
|
||||
{
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {}\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
{
|
||||
AgentDataReport agent_data;
|
||||
}
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {},\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"1\",\n"
|
||||
" \"c\": \"d\"\n"
|
||||
" }\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
EXPECT_TRUE(report->addAttr("a", "b"));
|
||||
EXPECT_TRUE(report->addAttr({{"c", "d"}, {"1", "2"}, {"delete", "me"}}));
|
||||
EXPECT_FALSE(report->addAttr("a", "d"));
|
||||
EXPECT_TRUE(report->addAttr("a", "1", true));
|
||||
report->deleteAttr("delete");
|
||||
{
|
||||
AgentDataReport agent_data;
|
||||
}
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"additionalMetaData\": {}\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
{
|
||||
AgentDataReport agent_data;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, advancedAttrTest)
|
||||
{
|
||||
// No EXPECT_CALL since attr list should be empty
|
||||
periodic_report();
|
||||
|
||||
EXPECT_TRUE(report->addAttr({{"c", "d"}, {"1", "2"}, {"send", "me"}}));
|
||||
EXPECT_TRUE(report->addAttr("a", "b"));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"b\",\n"
|
||||
" \"c\": \"d\",\n"
|
||||
" \"send\": \"me\"\n"
|
||||
" }\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
periodic_report();
|
||||
|
||||
EXPECT_FALSE(report->addAttr("a", "key exist so value not added"));
|
||||
// No second EXPECT_CALL since attr list was not updated after previous send
|
||||
periodic_report();
|
||||
|
||||
EXPECT_TRUE(report->addAttr("new", "key val"));
|
||||
EXPECT_TRUE(report->addAttr("a", "key val override", true));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
"{\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"key val override\",\n"
|
||||
" \"c\": \"d\",\n"
|
||||
" \"new\": \"key val\",\n"
|
||||
" \"send\": \"me\"\n"
|
||||
" }\n"
|
||||
"}",
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
periodic_report();
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, RestDetailsTest)
|
||||
{
|
||||
stringstream rest_call_parameters;
|
||||
rest_call_parameters
|
||||
<< "{\n"
|
||||
<< " \"attributes\": {\n"
|
||||
<< " \"1\": \"2\",\n"
|
||||
<< " \"a\": \"key val override\",\n"
|
||||
<< " \"c\": \"d\",\n"
|
||||
<< " \"send\": \"me\"\n"
|
||||
<< " }\n"
|
||||
<< "}";
|
||||
add_details_rest_cb->performRestCall(rest_call_parameters);
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
rest_call_parameters.str(),
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
|
||||
EXPECT_TRUE(report->sendAttributes());
|
||||
|
||||
is_server_mode = false;
|
||||
|
||||
EXPECT_CALL(mock_mainloop, addRecurringRoutine(_, _, _, "Report agent details attributes", _)).WillOnce(Return(2));
|
||||
|
||||
EXPECT_CALL(mock_rest, mockRestCall(RestAction::ADD, "agent-details-attr", _)).Times(0);
|
||||
agent_details_reporter_comp.init();
|
||||
|
||||
EXPECT_TRUE(report->addAttr("new", "key val"));
|
||||
::Flags<MessageConnConfig> conn_flags;
|
||||
conn_flags.setFlag(MessageConnConfig::ONE_TIME_CONN);
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
sendMessage(
|
||||
true,
|
||||
"{\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"key val override\",\n"
|
||||
" \"c\": \"d\",\n"
|
||||
" \"new\": \"key val\",\n"
|
||||
" \"send\": \"me\"\n"
|
||||
" }\n"
|
||||
"}",
|
||||
I_Messaging::Method::POST,
|
||||
string("127.0.0.1"),
|
||||
7777,
|
||||
conn_flags,
|
||||
string("add-agent-details-attr"),
|
||||
string(),
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(Maybe<string>(string("{\"status\":true}"))));
|
||||
|
||||
periodic_report();
|
||||
}
|
||||
|
||||
TEST_F(AgentReporterTest, PersistenceAttrTest)
|
||||
{
|
||||
// no expect call for send message since the attr were not added yet
|
||||
EXPECT_TRUE(report->sendAttributes());
|
||||
|
||||
ofstream write_attributes(persistence_attr_file.fname);
|
||||
ASSERT_TRUE(write_attributes.is_open());
|
||||
|
||||
string expected_attributes(
|
||||
"{\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"key val override\",\n"
|
||||
" \"c\": \"d\",\n"
|
||||
" \"send\": \"me\"\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
|
||||
write_attributes << expected_attributes;
|
||||
write_attributes.close();
|
||||
|
||||
EXPECT_CALL(mock_mainloop, addRecurringRoutine(_, _, _, "Report agent details attributes", _)).WillOnce(Return(2));
|
||||
EXPECT_CALL(mock_rest, mockRestCall(RestAction::ADD, "agent-details-attr", _)).WillOnce(Return(true));
|
||||
agent_details_reporter_comp.init();
|
||||
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_messaging,
|
||||
mockSendPersistentMessage(
|
||||
_,
|
||||
expected_attributes,
|
||||
I_Messaging::Method::PATCH,
|
||||
"/agents",
|
||||
_,
|
||||
_,
|
||||
MessageTypeTag::GENERIC
|
||||
)
|
||||
).WillOnce(Return(string()));
|
||||
EXPECT_TRUE(report->sendAttributes());
|
||||
|
||||
EXPECT_TRUE(report->addAttr("new attr", "to add before fini"));
|
||||
agent_details_reporter_comp.fini();
|
||||
|
||||
ifstream read_attributes(persistence_attr_file.fname);
|
||||
ASSERT_TRUE(read_attributes.is_open());
|
||||
|
||||
expected_attributes =
|
||||
"{\n"
|
||||
" \"attributes\": {\n"
|
||||
" \"1\": \"2\",\n"
|
||||
" \"a\": \"key val override\",\n"
|
||||
" \"c\": \"d\",\n"
|
||||
" \"new attr\": \"to add before fini\",\n"
|
||||
" \"send\": \"me\"\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
stringstream actual_attributes;
|
||||
actual_attributes << read_attributes.rdbuf();
|
||||
read_attributes.close();
|
||||
|
||||
EXPECT_EQ(actual_attributes.str(), expected_attributes);
|
||||
}
|
||||
1
core/attachments/CMakeLists.txt
Normal file
1
core/attachments/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory(http_configuration)
|
||||
3
core/attachments/http_configuration/CMakeLists.txt
Normal file
3
core/attachments/http_configuration/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(http_configuration http_configuration.cc)
|
||||
|
||||
add_subdirectory(http_configuration_ut)
|
||||
204
core/attachments/http_configuration/http_configuration.cc
Normal file
204
core/attachments/http_configuration/http_configuration.cc
Normal file
@@ -0,0 +1,204 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "http_configuration.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "cereal/types/vector.hpp"
|
||||
|
||||
#define DEFAULT_KEEP_ALIVE_INTERVAL_MSEC 30000
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
DebugConfig::save(cereal::JSONOutputArchive &archive) const
|
||||
{
|
||||
archive(
|
||||
cereal::make_nvp("clientIp", client),
|
||||
cereal::make_nvp("listeningIp", server),
|
||||
cereal::make_nvp("uriPrefix", uri),
|
||||
cereal::make_nvp("hostName", host),
|
||||
cereal::make_nvp("httpMethod", method),
|
||||
cereal::make_nvp("listeningPort", port)
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
DebugConfig::load(cereal::JSONInputArchive &archive)
|
||||
{
|
||||
try {
|
||||
archive(
|
||||
cereal::make_nvp("clientIp", client),
|
||||
cereal::make_nvp("listeningIp", server),
|
||||
cereal::make_nvp("uriPrefix", uri),
|
||||
cereal::make_nvp("hostName", host),
|
||||
cereal::make_nvp("httpMethod", method),
|
||||
cereal::make_nvp("listeningPort", port)
|
||||
);
|
||||
} catch (const cereal::Exception &) {
|
||||
client = "";
|
||||
server = "";
|
||||
uri = "";
|
||||
host = "";
|
||||
method = "";
|
||||
port = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
DebugConfig::operator==(const DebugConfig &another) const
|
||||
{
|
||||
return
|
||||
client == another.client &&
|
||||
server == another.server &&
|
||||
port == another.port &&
|
||||
method == another.method &&
|
||||
host == another.host &&
|
||||
uri == another.uri;
|
||||
}
|
||||
|
||||
int
|
||||
HttpAttachmentConfiguration::init(const string &conf_file)
|
||||
{
|
||||
try {
|
||||
ifstream file(conf_file);
|
||||
cereal::JSONInputArchive ar(file);
|
||||
load(ar);
|
||||
return 1;
|
||||
} catch (exception &e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HttpAttachmentConfiguration::save(cereal::JSONOutputArchive &archive) const
|
||||
{
|
||||
archive(
|
||||
cereal::make_nvp("context_values", dbg),
|
||||
cereal::make_nvp("ip_ranges", exclude_sources),
|
||||
cereal::make_nvp("dbg_level", getNumericalValue("dbg_level")),
|
||||
cereal::make_nvp("static_resources_path", getStringValue("static_resources_path")),
|
||||
cereal::make_nvp("is_fail_open_mode_enabled", getNumericalValue("is_fail_open_mode_enabled")),
|
||||
cereal::make_nvp("fail_open_timeout", getNumericalValue("fail_open_timeout")),
|
||||
cereal::make_nvp("is_fail_open_mode_hold_enabled", getNumericalValue("is_fail_open_mode_hold_enabled")),
|
||||
cereal::make_nvp("fail_open_hold_timeout", getNumericalValue("fail_open_hold_timeout")),
|
||||
cereal::make_nvp("sessions_per_minute_limit_verdict", getStringValue("sessions_per_minute_limit_verdict")),
|
||||
cereal::make_nvp("max_sessions_per_minute", getNumericalValue("max_sessions_per_minute")),
|
||||
cereal::make_nvp("res_proccessing_timeout_msec", getNumericalValue("res_proccessing_timeout_msec")),
|
||||
cereal::make_nvp("req_proccessing_timeout_msec", getNumericalValue("req_proccessing_timeout_msec")),
|
||||
cereal::make_nvp("registration_thread_timeout_msec", getNumericalValue("registration_thread_timeout_msec")),
|
||||
cereal::make_nvp("req_header_thread_timeout_msec", getNumericalValue("req_header_thread_timeout_msec")),
|
||||
cereal::make_nvp("req_body_thread_timeout_msec", getNumericalValue("req_body_thread_timeout_msec")),
|
||||
cereal::make_nvp("res_header_thread_timeout_msec", getNumericalValue("res_header_thread_timeout_msec")),
|
||||
cereal::make_nvp("res_body_thread_timeout_msec", getNumericalValue("res_body_thread_timeout_msec")),
|
||||
cereal::make_nvp(
|
||||
"waiting_for_verdict_thread_timeout_msec",
|
||||
getNumericalValue("waiting_for_verdict_thread_timeout_msec")
|
||||
),
|
||||
cereal::make_nvp("nginx_inspection_mode", getNumericalValue("inspection_mode")),
|
||||
cereal::make_nvp("num_of_nginx_ipc_elements", getNumericalValue("num_of_nginx_ipc_elements")),
|
||||
cereal::make_nvp("keep_alive_interval_msec", getNumericalValue("keep_alive_interval_msec"))
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
HttpAttachmentConfiguration::load(cereal::JSONInputArchive &archive)
|
||||
{
|
||||
try {
|
||||
archive(cereal::make_nvp("context_values", dbg));
|
||||
} catch (const cereal::Exception &) {
|
||||
dbg = DebugConfig();
|
||||
}
|
||||
|
||||
try {
|
||||
archive(cereal::make_nvp("ip_ranges", exclude_sources));
|
||||
} catch (const cereal::Exception &) {
|
||||
exclude_sources = {};
|
||||
}
|
||||
|
||||
try {
|
||||
string str;
|
||||
archive(cereal::make_nvp("static_resources_path", str));
|
||||
string_values["static_resources_path"] = str;
|
||||
} catch (const cereal::Exception &) {
|
||||
string_values.erase("static_resources_path");
|
||||
}
|
||||
|
||||
try {
|
||||
string str;
|
||||
archive(cereal::make_nvp("sessions_per_minute_limit_verdict", str));
|
||||
string_values["sessions_per_minute_limit_verdict"] = str;
|
||||
} catch (const cereal::Exception &) {
|
||||
string_values.erase("sessions_per_minute_limit_verdict");
|
||||
}
|
||||
|
||||
loadNumericalValue(archive, "dbg_level", 0);
|
||||
loadNumericalValue(archive, "is_fail_open_mode_enabled", 0);
|
||||
loadNumericalValue(archive, "fail_open_timeout", 50);
|
||||
loadNumericalValue(archive, "is_fail_open_mode_hold_enabled", 0);
|
||||
loadNumericalValue(archive, "fail_open_hold_timeout", 200);
|
||||
loadNumericalValue(archive, "sessions_per_minute_limit_verdict", 0);
|
||||
loadNumericalValue(archive, "max_sessions_per_minute", 0);
|
||||
loadNumericalValue(archive, "res_proccessing_timeout_msec", 3000);
|
||||
loadNumericalValue(archive, "req_proccessing_timeout_msec", 3000);
|
||||
loadNumericalValue(archive, "registration_thread_timeout_msec", 100);
|
||||
loadNumericalValue(archive, "req_header_thread_timeout_msec", 100);
|
||||
loadNumericalValue(archive, "req_body_thread_timeout_msec", 150);
|
||||
loadNumericalValue(archive, "res_header_thread_timeout_msec", 100);
|
||||
loadNumericalValue(archive, "res_body_thread_timeout_msec", 150);
|
||||
loadNumericalValue(archive, "waiting_for_verdict_thread_timeout_msec", 150);
|
||||
loadNumericalValue(archive, "nginx_inspection_mode", 0);
|
||||
loadNumericalValue(archive, "num_of_nginx_ipc_elements", 200);
|
||||
loadNumericalValue(archive, "keep_alive_interval_msec", DEFAULT_KEEP_ALIVE_INTERVAL_MSEC);
|
||||
}
|
||||
|
||||
bool
|
||||
HttpAttachmentConfiguration::operator==(const HttpAttachmentConfiguration &other) const
|
||||
{
|
||||
return
|
||||
dbg == other.dbg &&
|
||||
numerical_values == other.numerical_values &&
|
||||
string_values == other.string_values &&
|
||||
exclude_sources == other.exclude_sources;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
HttpAttachmentConfiguration::getNumericalValue(const string &key) const
|
||||
{
|
||||
auto elem = numerical_values.find(key);
|
||||
return elem != numerical_values.end() ? elem->second : 0;
|
||||
}
|
||||
|
||||
const string &
|
||||
HttpAttachmentConfiguration::getStringValue(const string &key) const
|
||||
{
|
||||
auto elem = string_values.find(key);
|
||||
return elem != string_values.end() ? elem->second : empty;
|
||||
}
|
||||
|
||||
void
|
||||
HttpAttachmentConfiguration::loadNumericalValue(
|
||||
cereal::JSONInputArchive &ar,
|
||||
const string &name,
|
||||
unsigned int default_value
|
||||
)
|
||||
{
|
||||
try {
|
||||
unsigned int value;
|
||||
ar(cereal::make_nvp(name, value));
|
||||
numerical_values[name] = value;
|
||||
} catch (const cereal::Exception &) {
|
||||
numerical_values[name] = default_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
add_unit_test(http_configuration_ut http_configuration_ut.cc http_configuration)
|
||||
@@ -0,0 +1,111 @@
|
||||
#include "http_configuration.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fstream>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "c_common/ip_common.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class HttpAttachmentUtilTest : public Test
|
||||
{
|
||||
public:
|
||||
string
|
||||
createIPRangesString(const vector<string> &ip_ranges)
|
||||
{
|
||||
stringstream ip_ranges_string_stream;
|
||||
ip_ranges_string_stream << "[";
|
||||
for (auto iterator = ip_ranges.begin(); iterator < ip_ranges.end() - 1; iterator++) {
|
||||
ip_ranges_string_stream << "\"" << *iterator << "\"" << ", ";
|
||||
}
|
||||
ip_ranges_string_stream << "\"" << ip_ranges.back() << "\"]";
|
||||
|
||||
return ip_ranges_string_stream.str();
|
||||
}
|
||||
|
||||
const string attachment_configuration_file_name = "cp_nano_http_attachment_conf";
|
||||
const vector<string> ip_ranges = { "8.8.8.8", "9.9.9.9-10.10.10.10", "0:0:0:0:0:0:0:1-0:0:0:0:0:0:0:4"};
|
||||
const string static_resources_path = "/dev/shm/static_resources/";
|
||||
};
|
||||
|
||||
TEST_F(HttpAttachmentUtilTest, GetValidAttachmentConfiguration)
|
||||
{
|
||||
string valid_configuration =
|
||||
"{\n"
|
||||
"\"context_values\": {"
|
||||
"\"clientIp\": \"1.2.3.4\","
|
||||
"\"listeningIp\": \"5.6.7.8\","
|
||||
"\"uriPrefix\": \"/abc\","
|
||||
"\"hostName\": \"test\","
|
||||
"\"httpMethod\": \"GET\","
|
||||
"\"listeningPort\": 80"
|
||||
"},"
|
||||
"\"is_fail_open_mode_enabled\": 0,\n"
|
||||
"\"fail_open_timeout\": 1234,\n"
|
||||
"\"is_fail_open_mode_hold_enabled\": 0,\n"
|
||||
"\"fail_open_hold_timeout\": 4321,\n"
|
||||
"\"sessions_per_minute_limit_verdict\": \"Accept\",\n"
|
||||
"\"max_sessions_per_minute\": 0,\n"
|
||||
"\"num_of_nginx_ipc_elements\": 200,\n"
|
||||
"\"keep_alive_interval_msec\": 10000,\n"
|
||||
"\"dbg_level\": 2,\n"
|
||||
"\"nginx_inspection_mode\": 1,\n"
|
||||
"\"operation_mode\": 0,\n"
|
||||
"\"req_body_thread_timeout_msec\": 155,\n"
|
||||
"\"req_proccessing_timeout_msec\": 42,\n"
|
||||
"\"registration_thread_timeout_msec\": 101,\n"
|
||||
"\"res_proccessing_timeout_msec\": 420,\n"
|
||||
"\"res_header_thread_timeout_msec\": 1,\n"
|
||||
"\"res_body_thread_timeout_msec\": 80,\n"
|
||||
"\"waiting_for_verdict_thread_timeout_msec\": 60,\n"
|
||||
"\"req_header_thread_timeout_msec\": 10,\n"
|
||||
"\"ip_ranges\": " + createIPRangesString(ip_ranges) + ",\n"
|
||||
"\"static_resources_path\": \"" + static_resources_path + "\""
|
||||
"}\n";
|
||||
ofstream valid_configuration_file(attachment_configuration_file_name);
|
||||
valid_configuration_file << valid_configuration;
|
||||
valid_configuration_file.close();
|
||||
|
||||
HttpAttachmentConfiguration conf_data_out;
|
||||
EXPECT_EQ(conf_data_out.init(attachment_configuration_file_name), 1);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("is_fail_open_mode_enabled"), 0);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("fail_open_timeout"), 1234);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("is_fail_open_mode_hold_enabled"), 0);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("fail_open_hold_timeout"), 4321);
|
||||
EXPECT_EQ(conf_data_out.getStringValue("sessions_per_minute_limit_verdict"), "Accept");
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("max_sessions_per_minute"), 0);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("num_of_nginx_ipc_elements"), 200);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("keep_alive_interval_msec"), 10000);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("dbg_level"), 2u);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("res_proccessing_timeout_msec"), 420);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("req_proccessing_timeout_msec"), 42);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("registration_thread_timeout_msec"), 101);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("req_header_thread_timeout_msec"), 10);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("req_body_thread_timeout_msec"), 155);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("res_header_thread_timeout_msec"), 1);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("res_body_thread_timeout_msec"), 80);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("waiting_for_verdict_thread_timeout_msec"), 60);
|
||||
EXPECT_EQ(conf_data_out.getNumericalValue("nginx_inspection_mode"), 1);
|
||||
}
|
||||
|
||||
TEST_F(HttpAttachmentUtilTest, GetMalformedAttachmentConfiguration)
|
||||
{
|
||||
string malformed_configuration =
|
||||
"{\n"
|
||||
"\"is_fail_open_mode_enabled\": false,,,,,,\n"
|
||||
"\"fail_open_timeout\": 1234,\n"
|
||||
"\"num_of_nginx_ipc_elements\": 200,\n"
|
||||
"\"dbg_level\": 2,\n"
|
||||
"\"ip_ranges\": " + createIPRangesString(ip_ranges) + ",\n"
|
||||
"\"static_resources_path\": \"" + static_resources_path + "\""
|
||||
"}\n";
|
||||
ofstream valid_configuration_file(attachment_configuration_file_name);
|
||||
valid_configuration_file << malformed_configuration;
|
||||
valid_configuration_file.close();
|
||||
|
||||
HttpAttachmentConfiguration conf_data_out;
|
||||
EXPECT_EQ(conf_data_out.init(attachment_configuration_file_name), 0);
|
||||
}
|
||||
3
core/buffers/CMakeLists.txt
Normal file
3
core/buffers/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(buffers buffer.cc char_iterator.cc data_container.cc segment.cc buffer_eval.cc)
|
||||
|
||||
add_subdirectory(buffers_ut)
|
||||
530
core/buffers/buffer.cc
Executable file
530
core/buffers/buffer.cc
Executable file
@@ -0,0 +1,530 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Buffer::Buffer(vector<u_char> &&vec)
|
||||
:
|
||||
len(vec.size())
|
||||
{
|
||||
if (len != 0) {
|
||||
segs.push_back(Segment(move(vec)));
|
||||
evalFastPath();
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Buffer(const vector<u_char> &vec)
|
||||
:
|
||||
Buffer(vec.data(), vec.size(), MemoryType::OWNED)
|
||||
{}
|
||||
|
||||
Buffer::Buffer(const vector<char> &vec)
|
||||
:
|
||||
Buffer(reinterpret_cast<const u_char *>(vec.data()), vec.size(), MemoryType::OWNED)
|
||||
{}
|
||||
|
||||
Buffer::Buffer(const string &str)
|
||||
:
|
||||
Buffer(reinterpret_cast<const u_char *>(str.data()), str.size(), MemoryType::OWNED)
|
||||
{}
|
||||
|
||||
Buffer::Buffer(const u_char *_ptr, uint _len, MemoryType type)
|
||||
:
|
||||
len(_len)
|
||||
{
|
||||
if (len != 0) {
|
||||
segs.emplace_back(_ptr, _len, type);
|
||||
evalFastPath();
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Buffer(const char *_ptr, uint _len, MemoryType type)
|
||||
:
|
||||
Buffer(reinterpret_cast<const u_char *>(_ptr), _len, type)
|
||||
{}
|
||||
|
||||
Buffer::Buffer(const Buffer &buf)
|
||||
:
|
||||
segs(buf.segs),
|
||||
len(buf.len)
|
||||
{
|
||||
evalFastPath();
|
||||
}
|
||||
|
||||
Buffer::Buffer(Buffer &&buf)
|
||||
:
|
||||
segs(move(buf.segs))
|
||||
{
|
||||
len = buf.len;
|
||||
evalFastPath();
|
||||
|
||||
buf.len = 0;
|
||||
buf.evalFastPath();
|
||||
}
|
||||
|
||||
Buffer &
|
||||
Buffer::operator=(const Buffer &buf)
|
||||
{
|
||||
segs = buf.segs;
|
||||
len = buf.len;
|
||||
evalFastPath();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer &
|
||||
Buffer::operator=(Buffer &&buf)
|
||||
{
|
||||
segs = move(buf.segs);
|
||||
len = buf.len;
|
||||
evalFastPath();
|
||||
|
||||
buf.len = 0;
|
||||
buf.evalFastPath();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::contains(char ch) const
|
||||
{
|
||||
for (const auto &iter : *this) {
|
||||
if (iter == ch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint
|
||||
Buffer::segmentsNumber() const
|
||||
{
|
||||
return segs.size();
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::operator+=(const Buffer &other)
|
||||
{
|
||||
if (other.len == 0) return;
|
||||
segs.insert(segs.end(), other.segs.begin(), other.segs.end());
|
||||
len += other.len;
|
||||
|
||||
// If the buffer was originally empty (and had no segments), fast path needs to be evaluated.
|
||||
// This can be detected by the fact that the length of the buffer is equal to the length of `other`.
|
||||
if (len == other.len) evalFastPath();
|
||||
}
|
||||
|
||||
Buffer
|
||||
Buffer::operator+(const Buffer &other) const
|
||||
{
|
||||
Buffer res;
|
||||
res.segs.reserve(segmentsNumber() + other.segmentsNumber());
|
||||
res.segs.insert(res.segs.end(), segs.begin(), segs.end());
|
||||
res.segs.insert(res.segs.end(), other.segs.begin(), other.segs.end());
|
||||
res.len = len + other.len;
|
||||
res.evalFastPath();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Buffer
|
||||
Buffer::getSubBuffer(uint start, uint end) const
|
||||
{
|
||||
dbgAssert(start<=end && end<=len) << "Buffer::getSubBuffer() returned: Illegal scoping of buffer";
|
||||
if (start == end) return Buffer();
|
||||
|
||||
Buffer res;
|
||||
uint offset = 0;
|
||||
for (const auto &seg : segs) {
|
||||
uint seg_end = offset + seg.size();
|
||||
if (seg_end <= start) {
|
||||
offset = seg_end;
|
||||
continue;
|
||||
}
|
||||
res.segs.push_back(seg);
|
||||
if (offset < start) {
|
||||
auto remove = start - offset;
|
||||
res.segs.back().len -= remove;
|
||||
res.segs.back().offset += remove;
|
||||
res.segs.back().ptr += remove;
|
||||
}
|
||||
if (seg_end > end) {
|
||||
auto remove = seg_end - end;
|
||||
res.segs.back().len -= remove;
|
||||
}
|
||||
if (seg_end >= end) break;
|
||||
offset = seg_end;
|
||||
}
|
||||
res.len = end - start;
|
||||
|
||||
res.evalFastPath();
|
||||
return res;
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
Buffer::findFirstOf(char ch, uint start) const
|
||||
{
|
||||
dbgAssert(start <= len) << "Buffer::findFirstOf() returned: Cannot set a start point after buffer's end";
|
||||
|
||||
for (; start < len; ++start) {
|
||||
if ((*this)[start] == ch) return start;
|
||||
}
|
||||
return genError("Not located");
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
Buffer::findFirstOf(const Buffer &buf, uint start) const
|
||||
{
|
||||
dbgAssert(start <= len) << "Buffer::findFirstOf() returned: Cannot set a start point after buffer's end";
|
||||
|
||||
for (; start + buf.size() <= len; ++start) {
|
||||
auto sub_buffer = getSubBuffer(start, start + buf.size());
|
||||
if (sub_buffer == buf) return start;
|
||||
}
|
||||
return genError("Not located");
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
Buffer::findFirstNotOf(char ch, uint start) const
|
||||
{
|
||||
dbgAssert(start <= len) << "Buffer::findFirstNotOf() returned: Cannot set a start point after buffer's end";
|
||||
for (; start < len; ++start) {
|
||||
if ((*this)[start] != ch) return start;
|
||||
}
|
||||
return genError("Everything is the same ch");
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
Buffer::findLastOf(char ch, uint start) const
|
||||
{
|
||||
dbgAssert(start <= len) << "Buffer::findLastOf() returned: Cannot set a start point after buffer's end";
|
||||
for (; 0 < start; --start) {
|
||||
if ((*this)[start - 1] == ch) return start - 1;
|
||||
}
|
||||
return genError("Not located");
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
Buffer::findLastNotOf(char ch, uint start) const
|
||||
{
|
||||
dbgAssert(start <= len) << "Buffer::findLastNotOf() returned: Cannot set a start point after buffer's end";
|
||||
for (; 0 < start; --start) {
|
||||
if ((*this)[start - 1] != ch) return start - 1;
|
||||
}
|
||||
return genError("Everything is the same ch");
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::truncateHead(uint size)
|
||||
{
|
||||
dbgAssert(size <= len) << "Cannot set a new start of buffer after the buffer's end";
|
||||
if (size == 0) return;
|
||||
if (size == len) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
while (segs.front().size() <= size) {
|
||||
size -= segs.front().size();
|
||||
len -= segs.front().size();
|
||||
segs.erase(segs.begin());
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
len -= size;
|
||||
segs.front().offset += size;
|
||||
segs.front().len -= size;
|
||||
segs.front().ptr += size;
|
||||
}
|
||||
|
||||
evalFastPath();
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::truncateTail(uint size)
|
||||
{
|
||||
dbgAssert(size <= len) << "Cannot set a new end of buffer after the buffer's end";
|
||||
if (size == 0) return;
|
||||
if (size == len) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
while (segs.back().size() <= size) {
|
||||
size -= segs.back().size();
|
||||
len -= segs.back().size();
|
||||
segs.pop_back();
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
len -= size;
|
||||
segs.back().len -= size;
|
||||
}
|
||||
|
||||
// Special case in which fixing fast path is really easy (as the first segment didn't change)
|
||||
if (len < fast_path_len) fast_path_len = len;
|
||||
}
|
||||
void
|
||||
Buffer::keepHead(uint size)
|
||||
{
|
||||
dbgAssert(size <= len) << "Cannot set a new end of buffer before the buffer's start";
|
||||
truncateTail(len - size);
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::keepTail(uint size)
|
||||
{
|
||||
dbgAssert(size <= len) << "Cannot set a new start of buffer after the buffer's end";
|
||||
truncateHead(len - size);
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::clear()
|
||||
{
|
||||
segs.clear();
|
||||
len = 0;
|
||||
evalFastPath();
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::operator==(const Buffer &buf) const
|
||||
{
|
||||
if (len != buf.len) return false;
|
||||
if (len == 0) return true;
|
||||
|
||||
// Initializing the iteration over `this` segments
|
||||
// Since the segments of the buffer may be unaliagned, `l_ptr` and `l_size` hold the current place in the
|
||||
// current segment and the size left in the segment.
|
||||
auto l_iter = segs.begin();
|
||||
auto l_ptr = l_iter->data();
|
||||
auto l_size = l_iter->size();
|
||||
// Same for `buf`
|
||||
auto r_iter = buf.segs.begin();
|
||||
auto r_ptr = r_iter->data();
|
||||
auto r_size = r_iter->size();
|
||||
// `offset` is the current offset in both buffers and is used to track the progess of the loop.
|
||||
uint offset = 0;
|
||||
|
||||
while (true) {
|
||||
uint curr_size = min(l_size, r_size); // Size to compare
|
||||
if (memcmp(l_ptr, r_ptr, curr_size) != 0) return false;
|
||||
offset += curr_size;
|
||||
|
||||
if (offset >= len) break;
|
||||
|
||||
if (l_size <= curr_size) { // Finished a segment of `this`
|
||||
l_iter++;
|
||||
l_ptr = l_iter->data();
|
||||
l_size = l_iter->size();
|
||||
} else { // Progress within the segment
|
||||
l_size -= curr_size;
|
||||
l_ptr += curr_size;
|
||||
}
|
||||
|
||||
if (r_size <= curr_size) { // Finished a segment of `buf`
|
||||
r_iter++;
|
||||
r_ptr = r_iter->data();
|
||||
r_size = r_iter->size();
|
||||
} else { // Progress within the segment
|
||||
r_size -= curr_size;
|
||||
r_ptr += curr_size;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::isEqual(const u_char *ptr, uint size) const
|
||||
{
|
||||
if (len != size) return false;
|
||||
for (const auto &seg : segs) {
|
||||
if (memcmp(ptr, seg.data(), seg.size()) != 0) return false;
|
||||
ptr += seg.size();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::isEqualLowerCase(const Buffer &buf) const
|
||||
{
|
||||
if (len != buf.size()) return false;
|
||||
for (uint i = 0; i < len; i++) {
|
||||
if (tolower((*this)[i]) != buf[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const u_char &
|
||||
Buffer::operator[](uint offset) const
|
||||
{
|
||||
if (offset < fast_path_len) {
|
||||
if (is_owned!=nullptr && *is_owned) {
|
||||
evalFastPath();
|
||||
}
|
||||
return fast_path_ptr[offset];
|
||||
}
|
||||
dbgAssert(offset < len) << "Buffer::operator returned: attempted an access outside the buffer";
|
||||
return *(begin() + offset);
|
||||
}
|
||||
|
||||
Buffer::operator string() const
|
||||
{
|
||||
serialize();
|
||||
return string(reinterpret_cast<const char *>(fast_path_ptr), fast_path_len);
|
||||
}
|
||||
|
||||
Maybe<Buffer::InternalPtr<u_char>>
|
||||
Buffer::getPtr(uint start, uint get_len) const
|
||||
{
|
||||
auto end = start + get_len;
|
||||
if (end > len) return genError("Cannot get internal pointer beyond the buffer limits");
|
||||
if (start >= end) return genError("Invalid length ('start' is not smaller than 'end')");
|
||||
|
||||
if (end <= fast_path_len) {
|
||||
if (is_owned!=nullptr && *is_owned) {
|
||||
evalFastPath();
|
||||
}
|
||||
return Buffer::InternalPtr<u_char>(fast_path_ptr + start, segs.front().data_container);
|
||||
}
|
||||
|
||||
// Search the segments for the one that contains the requested data.
|
||||
uint offset = 0;
|
||||
for (auto &seg : segs) {
|
||||
uint seg_end = offset + seg.size();
|
||||
if (seg_end < start) { // We haven't reached the segment yet
|
||||
offset = seg_end;
|
||||
continue;
|
||||
}
|
||||
if (seg_end < end) break; // The data isn't contained entirely in one segment, serialization is needed.
|
||||
|
||||
return Buffer::InternalPtr<u_char>(seg.ptr + start - offset, seg.data_container);
|
||||
}
|
||||
|
||||
serialize();
|
||||
return Buffer::InternalPtr<u_char>(fast_path_ptr + start, segs.front().data_container);
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::serialize() const
|
||||
{
|
||||
if (segmentsNumber() < 2) {
|
||||
evalFastPath();
|
||||
return;
|
||||
}
|
||||
// While `serialize` doesn't change the content of the buffer, it does changes the way it is organized internally.
|
||||
// Since we want to allow accesors be `const`, and hide from the user the fact that they require changing the
|
||||
// internal structure of the buffer - `serialize` needs to be able to change a `const` buffer, hence the dangarous
|
||||
// `const_cast` on `this`.
|
||||
auto &segments = const_cast<Buffer *>(this)->segs;
|
||||
vector<u_char> vec;
|
||||
vec.reserve(len);
|
||||
for (auto iter : segments) {
|
||||
vec.insert(vec.end(), iter.data(), iter.data() + iter.size());
|
||||
}
|
||||
segments.clear();
|
||||
segments.push_back(Segment(move(vec)));
|
||||
evalFastPath();
|
||||
}
|
||||
|
||||
Buffer::CharIterator
|
||||
Buffer::begin() const
|
||||
{
|
||||
return len == 0 ? CharIterator(segs.end()) : CharIterator(segs.begin(), segs.end(), 0);
|
||||
}
|
||||
|
||||
Buffer::CharIterator
|
||||
Buffer::end() const
|
||||
{
|
||||
return CharIterator(segs.end());
|
||||
}
|
||||
|
||||
Buffer::SegRange
|
||||
Buffer::segRange() const
|
||||
{
|
||||
return SegRange(segs.begin(), segs.end());
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::evalFastPath() const
|
||||
{
|
||||
// While `evalFastPath` doesn't change the content of the buffer, it does re-evaluate the fast path elements.
|
||||
// Since we can detect such change in a `const` method, `evalFastPath` needs to be able to change fast path
|
||||
// parameters of a `const` buffer - hence the dangarous `const_cast` on `this`.
|
||||
auto non_const_this = const_cast<Buffer *>(this);
|
||||
|
||||
if (segs.size() != 0) {
|
||||
auto &seg = segs.front();
|
||||
non_const_this->fast_path_len = seg.size();
|
||||
non_const_this->fast_path_ptr = seg.data();
|
||||
non_const_this->type = seg.type;
|
||||
non_const_this->is_owned = seg.is_owned;
|
||||
} else {
|
||||
non_const_this->fast_path_len = 0;
|
||||
non_const_this->fast_path_ptr = nullptr;
|
||||
non_const_this->type = Volatility::NONE;
|
||||
non_const_this->is_owned = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::operator<(const Buffer &buf) const
|
||||
{
|
||||
if (len != buf.len) return len < buf.len;
|
||||
if (len == 0) return false;
|
||||
|
||||
// Initializing the iteration over `this` segments
|
||||
// Since the segments of the buffer may be unaliagned, `l_ptr` and `l_size` hold the current place in the
|
||||
// current segment and the size left in the segment.
|
||||
auto l_iter = segs.begin();
|
||||
auto l_ptr = l_iter->data();
|
||||
auto l_size = l_iter->size();
|
||||
// Same for `buf`
|
||||
auto r_iter = buf.segs.begin();
|
||||
auto r_ptr = r_iter->data();
|
||||
auto r_size = r_iter->size();
|
||||
// `offset` is the current offset in both buffers and is used to track the progess of the loop.
|
||||
uint offset = 0;
|
||||
|
||||
while (true) {
|
||||
uint curr_size = min(l_size, r_size); // Size to compare
|
||||
int compare_result = memcmp(l_ptr, r_ptr, curr_size);
|
||||
if (compare_result < 0) return true;
|
||||
if (compare_result > 0) return false;
|
||||
offset += curr_size;
|
||||
|
||||
if (offset >= len) break;
|
||||
|
||||
if (l_size <= curr_size) { // Finished a segment of `this`
|
||||
l_iter++;
|
||||
l_ptr = l_iter->data();
|
||||
l_size = l_iter->size();
|
||||
} else { // Progress within the segment
|
||||
l_size -= curr_size;
|
||||
l_ptr += curr_size;
|
||||
}
|
||||
|
||||
if (r_size <= curr_size) { // Finished a segment of `buf`
|
||||
r_iter++;
|
||||
r_ptr = r_iter->data();
|
||||
r_size = r_iter->size();
|
||||
} else { // Progress within the segment
|
||||
r_size -= curr_size;
|
||||
r_ptr += curr_size;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
43
core/buffers/buffer_eval.cc
Normal file
43
core/buffers/buffer_eval.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "buffer.h"
|
||||
#include "environment/evaluator_templates.h"
|
||||
|
||||
using namespace EnvironmentHelper;
|
||||
|
||||
class ConstantBuffer : public Constant<Buffer>
|
||||
{
|
||||
public:
|
||||
ConstantBuffer(const std::vector<std::string> ¶ms)
|
||||
:
|
||||
Constant<Buffer>(
|
||||
[] (const std::string &str) { return Buffer(str); },
|
||||
params
|
||||
) {}
|
||||
static std::string getName() { return Constant<Buffer>::getName() + "Buffer"; }
|
||||
};
|
||||
|
||||
class EqualBuffer : public Equal<Buffer>
|
||||
{
|
||||
public:
|
||||
EqualBuffer(const std::vector<std::string> ¶ms) : Equal<Buffer>(params) {}
|
||||
static std::string getName() { return Equal<Buffer>::getName() + "Buffer"; }
|
||||
};
|
||||
|
||||
void
|
||||
Buffer::preload()
|
||||
{
|
||||
addMatcher<ConstantBuffer>();
|
||||
addMatcher<EqualBuffer>();
|
||||
}
|
||||
7
core/buffers/buffers_ut/CMakeLists.txt
Normal file
7
core/buffers/buffers_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
buffers_ut
|
||||
"buffers_ut.cc;buffer_eval_ut.cc"
|
||||
"buffers;event_is;metric;-lboost_regex"
|
||||
)
|
||||
54
core/buffers/buffers_ut/buffer_eval_ut.cc
Normal file
54
core/buffers/buffers_ut/buffer_eval_ut.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "buffer.h"
|
||||
#include "cptest.h"
|
||||
#include "cptest.h"
|
||||
#include "environment.h"
|
||||
#include "singleton.h"
|
||||
#include "environment_evaluator.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class BuffferEval : public Test
|
||||
{
|
||||
public:
|
||||
BuffferEval()
|
||||
{
|
||||
env.preload();
|
||||
Buffer::preload();
|
||||
env.init();
|
||||
auto i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
i_env->getConfigurationContext().registerValue("buf_a", buf_a);
|
||||
i_env->getConfigurationContext().registerValue("buf_b", buf_b);
|
||||
}
|
||||
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
Buffer buf_a{"aaa"};
|
||||
Buffer buf_b{"bbb"};
|
||||
};
|
||||
|
||||
static ostream & operator<<(ostream &os, const Context::Error &) { return os; }
|
||||
|
||||
TEST_F(BuffferEval, compare)
|
||||
{
|
||||
auto eval_eq = genEvaluator<bool>("EqualBuffer(Get(buf_a), Get(buf_a))");
|
||||
EXPECT_TRUE(eval_eq.ok());
|
||||
EXPECT_THAT((*eval_eq)(), IsValue(true));
|
||||
|
||||
auto eval_nq = genEvaluator<bool>("EqualBuffer(Get(buf_a), Get(buf_b))");
|
||||
EXPECT_TRUE(eval_nq.ok());
|
||||
EXPECT_THAT((*eval_nq)(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(BuffferEval, constant)
|
||||
{
|
||||
auto const_a = genEvaluator<Buffer>("ConstantBuffer(aaa)");
|
||||
EXPECT_TRUE(const_a.ok());
|
||||
EXPECT_THAT((*const_a)(), IsValue(buf_a));
|
||||
}
|
||||
733
core/buffers/buffers_ut/buffers_ut.cc
Normal file
733
core/buffers/buffers_ut/buffers_ut.cc
Normal file
@@ -0,0 +1,733 @@
|
||||
#include <string>
|
||||
|
||||
#include "cereal/archives/json.hpp"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "debug.h"
|
||||
#include "buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class BuffersTest : public Test
|
||||
{
|
||||
public:
|
||||
Buffer
|
||||
genBuf(string s1, string s2, string s3)
|
||||
{
|
||||
Buffer b1(s1), b2(s2), b3(s3);
|
||||
return b1 + b2 + b3;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BuffersTest, empty_buffer_dump)
|
||||
{
|
||||
Buffer buf;
|
||||
EXPECT_EQ(buf.size(), 0u);
|
||||
EXPECT_EQ(dumpHex(buf), "");
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, empty_buffer)
|
||||
{
|
||||
Buffer buf;
|
||||
EXPECT_EQ(buf.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, basic_content_string)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
EXPECT_EQ(buf[0], '1');
|
||||
EXPECT_EQ(buf[1], '2');
|
||||
EXPECT_EQ(buf[2], '3');
|
||||
EXPECT_EQ(buf[3], '4');
|
||||
EXPECT_EQ(buf[4], '5');
|
||||
EXPECT_EQ(buf[5], '6');
|
||||
EXPECT_EQ(buf[6], '7');
|
||||
EXPECT_EQ(buf[7], '8');
|
||||
EXPECT_EQ(buf[8], '9');
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, basic_content_uchar_vec)
|
||||
{
|
||||
vector<u_char> vec = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
Buffer buf(vec);
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
EXPECT_EQ(buf[0], '1');
|
||||
EXPECT_EQ(buf[1], '2');
|
||||
EXPECT_EQ(buf[2], '3');
|
||||
EXPECT_EQ(buf[3], '4');
|
||||
EXPECT_EQ(buf[4], '5');
|
||||
EXPECT_EQ(buf[5], '6');
|
||||
EXPECT_EQ(buf[6], '7');
|
||||
EXPECT_EQ(buf[7], '8');
|
||||
EXPECT_EQ(buf[8], '9');
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, basic_content_char_vec)
|
||||
{
|
||||
vector<char> vec = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
Buffer buf(vec);
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
EXPECT_EQ(buf[0], '1');
|
||||
EXPECT_EQ(buf[1], '2');
|
||||
EXPECT_EQ(buf[2], '3');
|
||||
EXPECT_EQ(buf[3], '4');
|
||||
EXPECT_EQ(buf[4], '5');
|
||||
EXPECT_EQ(buf[5], '6');
|
||||
EXPECT_EQ(buf[6], '7');
|
||||
EXPECT_EQ(buf[7], '8');
|
||||
EXPECT_EQ(buf[8], '9');
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, compare)
|
||||
{
|
||||
Buffer buf1("123456789");
|
||||
Buffer buf2("123456789");
|
||||
EXPECT_TRUE(buf1 == buf2);
|
||||
EXPECT_FALSE(buf1 != buf2);
|
||||
EXPECT_FALSE(buf1 < buf2);
|
||||
EXPECT_TRUE(buf1 <= buf2);
|
||||
EXPECT_FALSE(buf1 > buf2);
|
||||
EXPECT_TRUE(buf1 >= buf2);
|
||||
|
||||
Buffer buf3("12345678");
|
||||
EXPECT_FALSE(buf1 == buf3);
|
||||
EXPECT_TRUE(buf1 != buf3);
|
||||
EXPECT_FALSE(buf1 < buf3);
|
||||
EXPECT_TRUE(buf3 < buf1);
|
||||
EXPECT_FALSE(buf1 <= buf3);
|
||||
EXPECT_TRUE(buf3 <= buf1);
|
||||
EXPECT_TRUE(buf1 > buf3);
|
||||
EXPECT_FALSE(buf3 > buf1);
|
||||
EXPECT_TRUE(buf1 >= buf3);
|
||||
EXPECT_FALSE(buf3 >= buf1);
|
||||
|
||||
Buffer buf4("1234*6789");
|
||||
EXPECT_FALSE(buf1 == buf4);
|
||||
EXPECT_TRUE(buf1 != buf4);
|
||||
EXPECT_FALSE(buf1 < buf4);
|
||||
EXPECT_TRUE(buf4 < buf1);
|
||||
EXPECT_FALSE(buf1 <= buf4);
|
||||
EXPECT_TRUE(buf4 <= buf1);
|
||||
EXPECT_TRUE(buf1 > buf4);
|
||||
EXPECT_FALSE(buf4 > buf1);
|
||||
EXPECT_TRUE(buf1 >= buf4);
|
||||
EXPECT_FALSE(buf4 >= buf1);
|
||||
|
||||
Buffer buf5("1234067890");
|
||||
EXPECT_FALSE(buf1 == buf5);
|
||||
EXPECT_TRUE(buf1 != buf5);
|
||||
EXPECT_TRUE(buf1 < buf5);
|
||||
EXPECT_FALSE(buf5 < buf1);
|
||||
EXPECT_TRUE(buf1 <= buf5);
|
||||
EXPECT_FALSE(buf5 <= buf1);
|
||||
EXPECT_FALSE(buf1 > buf5);
|
||||
EXPECT_TRUE(buf5 > buf1);
|
||||
EXPECT_FALSE(buf1 >= buf5);
|
||||
EXPECT_TRUE(buf5 >= buf1);
|
||||
|
||||
Buffer buf6("");
|
||||
EXPECT_FALSE(buf1 < buf6);
|
||||
EXPECT_TRUE(buf6 < buf1);
|
||||
EXPECT_FALSE(buf1 <= buf6);
|
||||
EXPECT_TRUE(buf6 <= buf1);
|
||||
EXPECT_TRUE(buf1 > buf6);
|
||||
EXPECT_FALSE(buf6 > buf1);
|
||||
EXPECT_TRUE(buf1 >= buf6);
|
||||
EXPECT_FALSE(buf6 >= buf1);
|
||||
|
||||
Buffer buf7("");
|
||||
EXPECT_FALSE(buf7 < buf6);
|
||||
EXPECT_FALSE(buf6 < buf7);
|
||||
EXPECT_TRUE(buf7 <= buf6);
|
||||
EXPECT_TRUE(buf6 <= buf7);
|
||||
EXPECT_FALSE(buf7 > buf6);
|
||||
EXPECT_FALSE(buf6 > buf7);
|
||||
EXPECT_TRUE(buf7 >= buf6);
|
||||
EXPECT_TRUE(buf6 >= buf7);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, truncate_head)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
buf.truncateHead(6);
|
||||
EXPECT_EQ(buf, Buffer("789"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, truncate_tail)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
buf.truncateTail(4);
|
||||
EXPECT_EQ(buf, Buffer("12345"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, keep_head)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
buf.keepHead(6);
|
||||
EXPECT_EQ(buf, Buffer("123456"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, keep_tail)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
buf.keepTail(4);
|
||||
EXPECT_EQ(buf, Buffer("6789"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, slicing_final)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
Buffer b1 = buf, b2 = buf;
|
||||
b1.truncateHead(3); // "456789"
|
||||
b1.truncateTail(3); // "456"
|
||||
b2.truncateTail(3); // "123456"
|
||||
b2.truncateHead(3); // "456"
|
||||
EXPECT_EQ(b1, b2);
|
||||
b2.truncateHead(1); // "45"
|
||||
EXPECT_NE(b1, b2);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, data)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
EXPECT_EQ(bcmp(buf.data(), "123456789", 9), 0);
|
||||
}
|
||||
|
||||
struct TestStruct
|
||||
{
|
||||
char first;
|
||||
char second;
|
||||
};
|
||||
|
||||
TEST_F(BuffersTest, casting)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
auto test = buf.getTypePtr<struct TestStruct>(2).unpack();
|
||||
EXPECT_EQ(test->first, '3');
|
||||
EXPECT_EQ(test->second, '4');
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, casting_fail)
|
||||
{
|
||||
Buffer buf("123456789");
|
||||
auto test = buf.getTypePtr<struct TestStruct>(8);
|
||||
EXPECT_THAT(test, IsError("Cannot get internal pointer beyond the buffer limits"));
|
||||
test = buf.getTypePtr<struct TestStruct>(-1);
|
||||
EXPECT_THAT(test, IsError("Invalid length ('start' is not smaller than 'end')"));
|
||||
test = buf.getTypePtr<struct TestStruct>(9);
|
||||
EXPECT_THAT(test, IsError("Cannot get internal pointer beyond the buffer limits"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, death_on_asserts)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
Buffer buf1("123456789");
|
||||
EXPECT_DEATH(buf1[10], "Buffer::operator returned: attempted an access outside the buffer");
|
||||
EXPECT_DEATH(buf1[-1], "Buffer::operator returned: attempted an access outside the buffer");
|
||||
EXPECT_DEATH(buf1.truncateHead(10), "Cannot set a new start of buffer after the buffer's end");
|
||||
EXPECT_DEATH(buf1.truncateTail(10), "Cannot set a new end of buffer after the buffer's end");
|
||||
EXPECT_DEATH(buf1.keepHead(10), "Cannot set a new end of buffer before the buffer's start");
|
||||
EXPECT_DEATH(buf1.keepTail(10), "Cannot set a new start of buffer after the buffer's end");
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, basic_content2)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
EXPECT_EQ(buf[0], '1');
|
||||
EXPECT_EQ(buf[1], '2');
|
||||
EXPECT_EQ(buf[2], '3');
|
||||
EXPECT_EQ(buf[3], '4');
|
||||
EXPECT_EQ(buf[4], '5');
|
||||
EXPECT_EQ(buf[5], '6');
|
||||
EXPECT_EQ(buf[6], '7');
|
||||
EXPECT_EQ(buf[7], '8');
|
||||
EXPECT_EQ(buf[8], '9');
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, compare_buffers)
|
||||
{
|
||||
auto buf1 = genBuf("123", "456", "789");
|
||||
auto buf2 = genBuf("12", "3456", "789");
|
||||
EXPECT_TRUE(buf1 == buf2);
|
||||
EXPECT_FALSE(buf1 != buf2);
|
||||
|
||||
auto buf3 = genBuf("123", "46", "789");
|
||||
EXPECT_FALSE(buf1 == buf3);
|
||||
EXPECT_TRUE(buf1 != buf3);
|
||||
|
||||
auto buf4 = genBuf("123", "406", "789");
|
||||
EXPECT_FALSE(buf1 == buf4);
|
||||
EXPECT_TRUE(buf1 != buf4);
|
||||
|
||||
auto buf5 = genBuf("123", "456", "7890");
|
||||
EXPECT_FALSE(buf1 == buf5);
|
||||
EXPECT_TRUE(buf1 != buf5);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, truncate_head2)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
buf.truncateHead(5);
|
||||
EXPECT_EQ(buf, Buffer("6789"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, truncate_tail2)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
buf.truncateTail(4);
|
||||
EXPECT_EQ(buf, Buffer("12345"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, sub_buffer)
|
||||
{
|
||||
auto origbuf = genBuf("123", "456", "789");
|
||||
auto subbuf = origbuf.getSubBuffer(4, 7);
|
||||
EXPECT_EQ(subbuf, Buffer("567"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, add_compound)
|
||||
{
|
||||
auto buf = genBuf("1", "2", "3");
|
||||
// Testing adding a buffer to itself, which is an extreme case.
|
||||
buf += buf;
|
||||
EXPECT_EQ(buf, Buffer("123123"));
|
||||
}
|
||||
|
||||
string
|
||||
iterToStr(const Buffer::SegIterator &iter)
|
||||
{
|
||||
return string(reinterpret_cast<const char *>(iter->data()), iter->size());
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, add_operator_of_iterator)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
auto iter = buf.segRange().begin();
|
||||
EXPECT_EQ(iterToStr(iter), "12");
|
||||
iter++;
|
||||
EXPECT_EQ(iterToStr(iter), "3456");
|
||||
iter++;
|
||||
EXPECT_EQ(iterToStr(iter), "789");
|
||||
iter++;
|
||||
EXPECT_TRUE(iter == buf.segRange().end());
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const vector<string> &vec, const Buffer &buf)
|
||||
{
|
||||
auto vec_iter = vec.begin();
|
||||
for (auto &iter : buf.segRange()) {
|
||||
if (vec_iter == vec.end()) return false;
|
||||
if (iter != *vec_iter) return false;
|
||||
vec_iter++;
|
||||
}
|
||||
return vec_iter == vec.end();
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const string &str, const Buffer::Segment &seg)
|
||||
{
|
||||
string tmp(reinterpret_cast<const char *>(seg.data()), seg.size());
|
||||
return str == tmp;
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, iterator_loop)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
vector<string> vec = { "12", "3456", "789" };
|
||||
|
||||
auto vec_iter = vec.begin();
|
||||
for (auto seg : buf.segRange()) {
|
||||
EXPECT_EQ(*vec_iter, seg);
|
||||
vec_iter++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, flatten)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
EXPECT_EQ((vector<string>{ "12", "3456", "789" }), buf);
|
||||
|
||||
buf.serialize();
|
||||
EXPECT_EQ((vector<string>{ "123456789" }), buf);
|
||||
|
||||
auto buf2 = genBuf("12", "3456", "789");
|
||||
buf2.truncateHead(1); // "23456789"
|
||||
buf2.truncateTail(1); // "2345678"
|
||||
|
||||
buf2.serialize();
|
||||
EXPECT_EQ((vector<string>{ "2345678" }), buf2);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, get_pointer)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
auto ptr1 = buf.getPtr(3, 3);
|
||||
ASSERT_TRUE(ptr1.ok());
|
||||
// Internal Structure of the buffer didn't change.
|
||||
EXPECT_EQ((vector<string>{ "12", "3456", "789" }), buf);
|
||||
// Get the currect segment
|
||||
auto iter = buf.segRange().begin();
|
||||
iter++;
|
||||
// Check that the internal pointer points to the segment in the correct location.
|
||||
EXPECT_EQ(iter->data() + 1, ptr1.unpack());
|
||||
|
||||
auto ptr2 = buf.getPtr(5, 2);
|
||||
ASSERT_TRUE(ptr2.ok());
|
||||
// Buffer had to be serialized
|
||||
EXPECT_EQ((vector<string>{ "123456789" }), buf);
|
||||
// Check that the internal pointer points to the correct point in the serialized data.
|
||||
EXPECT_EQ(buf.data() + 5, ptr2.unpack());
|
||||
|
||||
auto ptr3 = buf.getPtr(5, 25);
|
||||
EXPECT_THAT(ptr3, IsError("Cannot get internal pointer beyond the buffer limits"));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, InternalPtr_assign)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
auto ptr1 = buf.getPtr(3, 3);
|
||||
ASSERT_TRUE(ptr1.ok());
|
||||
auto ptr2 = ptr1;
|
||||
ASSERT_TRUE(ptr1.ok());
|
||||
ASSERT_TRUE(ptr2.ok());
|
||||
EXPECT_EQ(ptr1.unpack(), ptr2.unpack());
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, InternalPtr_move)
|
||||
{
|
||||
auto buf = genBuf("12", "3456", "789");
|
||||
auto ptr1 = buf.getPtr(3, 6);
|
||||
ASSERT_TRUE(ptr1.ok());
|
||||
auto ptr2 = buf.getPtr(2, 5);
|
||||
ASSERT_TRUE(ptr2.ok());
|
||||
ptr2 = move(ptr1);
|
||||
//Move assignment operator takes over the resources of ptr1.
|
||||
EXPECT_EQ(ptr1.unpack(), nullptr);
|
||||
ASSERT_TRUE(ptr2.ok());
|
||||
EXPECT_EQ(buf.data() + 3, ptr2.unpack());
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, death_on_asserts2)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
EXPECT_DEATH(buf[10], "Buffer::operator returned: attempted an access outside the buffer");
|
||||
EXPECT_DEATH(buf[-1], "Buffer::operator returned: attempted an access outside the buffer");
|
||||
EXPECT_DEATH(buf.truncateTail(10), "Cannot set a new end of buffer after the buffer's end");
|
||||
EXPECT_DEATH(buf.truncateHead(10), "Cannot set a new start of buffer after the buffer's end");
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, owned_data)
|
||||
{
|
||||
string str("0");
|
||||
auto ptr = reinterpret_cast<const u_char *>(str.data());
|
||||
Buffer b;
|
||||
{
|
||||
// OWNED memory copies the memory, so changes to the original pointer don't impact it.
|
||||
Buffer c(str.data(), 1, Buffer::MemoryType::OWNED);
|
||||
b = c;
|
||||
str[0] = '1';
|
||||
EXPECT_EQ(Buffer("0"), b);
|
||||
EXPECT_NE(ptr, b.data());
|
||||
}
|
||||
EXPECT_NE(ptr, b.data());
|
||||
str[0] = '2';
|
||||
EXPECT_EQ(Buffer("0"), b);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, static_data)
|
||||
{
|
||||
string str("0");
|
||||
auto ptr = reinterpret_cast<const u_char *>(str.data());
|
||||
Buffer b;
|
||||
{
|
||||
// STATIC always points to the original pointer.
|
||||
// In real scenarios `str` should be `static const` string and not a local changing varialbe, we absue it in
|
||||
// this case specifically so the behavoir of the memory can be shown.
|
||||
Buffer c(str.data(), 1, Buffer::MemoryType::STATIC);
|
||||
b = c;
|
||||
str[0] = '1';
|
||||
EXPECT_EQ(Buffer("1"), b);
|
||||
EXPECT_EQ(ptr, b.data());
|
||||
}
|
||||
str[0] = '2';
|
||||
EXPECT_EQ(Buffer("2"), b);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, volatile_data)
|
||||
{
|
||||
string str("0");
|
||||
auto ptr = reinterpret_cast<const u_char *>(str.data());
|
||||
Buffer b;
|
||||
{
|
||||
// VOLATILE memory only pointers to the original pointer while the initial instance lives.
|
||||
Buffer c(str.data(), 1, Buffer::MemoryType::VOLATILE);
|
||||
b = c;
|
||||
str[0] = '1';
|
||||
EXPECT_EQ(Buffer("1"), b);
|
||||
EXPECT_EQ(ptr, b.data());
|
||||
}
|
||||
EXPECT_NE(ptr, b.data());
|
||||
// Memory was copied, so further changes don't impact it.
|
||||
str[0] = '2';
|
||||
EXPECT_EQ(Buffer("1"), b);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, truncate_volatile_data)
|
||||
{
|
||||
string str("123");
|
||||
Buffer b;
|
||||
{
|
||||
Buffer c(str.data(), 3, Buffer::MemoryType::VOLATILE);
|
||||
b = c;
|
||||
b.truncateHead(1);
|
||||
}
|
||||
EXPECT_EQ(Buffer("23"), b);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, clear)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
EXPECT_EQ(buf.size(), 9u);
|
||||
buf.clear();
|
||||
EXPECT_EQ(buf.size(), 0u);
|
||||
Buffer test = buf;
|
||||
EXPECT_EQ(test.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, access_after_clear)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
buf.clear();
|
||||
cptestPrepareToDie();
|
||||
EXPECT_DEATH(buf[1], "Buffer::operator() returned: attempted an access outside the buffer");
|
||||
EXPECT_DEATH(buf[0], "Buffer::operator() returned: attempted an access outside the buffer");
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, isEmpty)
|
||||
{
|
||||
auto b = genBuf("123", "456", "789");
|
||||
EXPECT_FALSE(b.isEmpty());
|
||||
b.clear();
|
||||
EXPECT_TRUE(b.isEmpty());
|
||||
Buffer c = b;
|
||||
EXPECT_TRUE(c.isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, contains)
|
||||
{
|
||||
vector<u_char> vec1 = { '1', '3', '5' };
|
||||
auto b1 = Buffer(vec1);
|
||||
|
||||
for (const char ch : vec1) {
|
||||
EXPECT_TRUE(b1.contains(ch));
|
||||
}
|
||||
|
||||
EXPECT_FALSE(b1.contains('?'));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, segmentsNumber)
|
||||
{
|
||||
vector<u_char> vec1 = { '1', '3', '7' };
|
||||
vector<u_char> vec2 = { '1', '3', '7' };
|
||||
auto b = Buffer(vec1) + Buffer(vec2);
|
||||
EXPECT_EQ(b.segmentsNumber(), 2u);
|
||||
EXPECT_EQ(b.size(), 6u);
|
||||
auto sub_b = b.getSubBuffer(0, 2);
|
||||
EXPECT_EQ(sub_b.segmentsNumber(), 1u);
|
||||
sub_b.clear();
|
||||
EXPECT_EQ(sub_b.segmentsNumber(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, Equl_buffers)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
const char* str = "1234567890";
|
||||
const u_char* u_str = reinterpret_cast<const u_char *>("1234567890");
|
||||
EXPECT_TRUE(buf.isEqual(str, 9));
|
||||
EXPECT_FALSE(buf.isEqual(str, 10));
|
||||
EXPECT_TRUE(buf.isEqual(u_str, 9));
|
||||
EXPECT_FALSE(buf.isEqual(u_str, 10));
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, string_casting)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
EXPECT_EQ(static_cast<string>(buf), string("123456789"));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, CharIterator)
|
||||
{
|
||||
auto buf = genBuf("123", "456", "789");
|
||||
vector<u_char> test_vec;
|
||||
for (auto iter : buf) {
|
||||
test_vec.push_back(iter);
|
||||
}
|
||||
vector<u_char> expect_vec = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
EXPECT_EQ(test_vec, expect_vec);
|
||||
|
||||
auto it = buf.begin() + 2;
|
||||
EXPECT_EQ(*(it), '3');
|
||||
it += 2;
|
||||
EXPECT_EQ(*(it), '5');
|
||||
++it;
|
||||
EXPECT_EQ(*(it), '6');
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, empty_CharIterator)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
auto it = Buffer::CharIterator();
|
||||
EXPECT_DEATH(*(it), "Buffer::CharIterator is not pointing to a real value");
|
||||
}
|
||||
|
||||
TEST_F(BuffersTest, serialization)
|
||||
{
|
||||
stringstream stream;
|
||||
{
|
||||
cereal::JSONOutputArchive ar(stream);
|
||||
ar(genBuf("aaa", "bb", "c"));
|
||||
}
|
||||
Buffer buf;
|
||||
{
|
||||
cereal::JSONInputArchive ar(stream);
|
||||
ar(buf);
|
||||
}
|
||||
EXPECT_EQ(buf, Buffer("aaabbc"));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, find_first_of_ch)
|
||||
{
|
||||
Buffer b1("boundary=Heeelllo;extrastuff;");
|
||||
uint index = b1.findFirstOf('=').unpack();
|
||||
EXPECT_TRUE(b1[index] == '=');
|
||||
EXPECT_TRUE(index == 8);
|
||||
Buffer b2("boundary");
|
||||
EXPECT_TRUE(b2 == b1.getSubBuffer(0, index));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, find_first_of_buf)
|
||||
{
|
||||
Buffer b1("boundary=Heeelllo;extrastuff;");
|
||||
Buffer find("=Heeel");
|
||||
uint index = b1.findFirstOf(find).unpack();
|
||||
EXPECT_TRUE(b1[index] == '=');
|
||||
EXPECT_TRUE(index == 8);
|
||||
Buffer b2("boundary");
|
||||
EXPECT_TRUE(b2 == b1.getSubBuffer(0, index));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, find_last_of)
|
||||
{
|
||||
Buffer b1("boundary=Heeelllo;extrastuff;");
|
||||
auto index = b1.findLastOf('u');
|
||||
EXPECT_TRUE(index.ok());
|
||||
EXPECT_TRUE(b1[index.unpack()] == 'u');
|
||||
EXPECT_TRUE(index.unpack() == 25);
|
||||
Buffer b2("boundary=Heeelllo;extrast");
|
||||
EXPECT_TRUE(b2 == b1.getSubBuffer(0, index.unpack()));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, find_first_not_of)
|
||||
{
|
||||
Buffer b1(" boundary ");
|
||||
auto index = b1.findFirstNotOf(' ');
|
||||
EXPECT_TRUE(index.ok());
|
||||
EXPECT_TRUE(b1[index.unpack()] == 'b');
|
||||
EXPECT_TRUE(index.unpack() == 4);
|
||||
Buffer b2(" ");
|
||||
EXPECT_TRUE(b2 == b1.getSubBuffer(0, index.unpack()));
|
||||
}
|
||||
|
||||
TEST_F (BuffersTest, find_last_not_of)
|
||||
{
|
||||
Buffer b1(" boundary ");
|
||||
auto index = b1.findLastNotOf(' ');
|
||||
EXPECT_TRUE(index.ok());
|
||||
EXPECT_TRUE(b1[index.unpack()] == 'y');
|
||||
EXPECT_TRUE(index.unpack() == 11);
|
||||
Buffer b2(" boundar");
|
||||
EXPECT_TRUE(b2 == b1.getSubBuffer(0, index.unpack()));
|
||||
}
|
||||
|
||||
class SegmentsTest: public Test
|
||||
{
|
||||
public:
|
||||
Buffer::Segment
|
||||
genSeg(const string &str, Buffer::MemoryType type = Buffer::MemoryType::OWNED)
|
||||
{
|
||||
Buffer::Segment seg(reinterpret_cast<const u_char *>(str.c_str()), str.length(), type);
|
||||
return seg;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
TEST_F(SegmentsTest, empty_segmnet)
|
||||
{
|
||||
Buffer::Segment seg;
|
||||
EXPECT_EQ(seg.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_F (SegmentsTest, assign)
|
||||
{
|
||||
Buffer::Segment seg1 = genSeg("123456789");
|
||||
Buffer::Segment seg2 = seg1;
|
||||
EXPECT_EQ(seg1.size(), seg2.size());
|
||||
EXPECT_EQ(bcmp(seg1.data(), seg2.data(), 9), 0);
|
||||
Buffer::Segment seg3;
|
||||
seg3 = seg2;
|
||||
EXPECT_EQ(seg3.size(), 9u);
|
||||
EXPECT_EQ(seg2.size(), 9u);
|
||||
EXPECT_EQ(seg2.data(), seg3.data());
|
||||
EXPECT_EQ(seg1.size(), seg3.size());
|
||||
}
|
||||
|
||||
TEST_F (SegmentsTest, move)
|
||||
{
|
||||
Buffer::Segment seg1 = genSeg("123456789");
|
||||
EXPECT_EQ(seg1.size(), 9u);
|
||||
Buffer::Segment seg2 = std::move(seg1);
|
||||
EXPECT_EQ(seg1.size(), 0u);
|
||||
EXPECT_EQ(seg2.size(), 9u);
|
||||
EXPECT_EQ(seg1.data(), nullptr);
|
||||
EXPECT_EQ(bcmp(seg2.data(), "123456789", 9), 0);
|
||||
Buffer::Segment seg3;
|
||||
seg3 = (std::move(seg2));
|
||||
EXPECT_EQ(seg2.size(), 0u);
|
||||
EXPECT_EQ(seg3.size(), 9u);
|
||||
EXPECT_EQ(seg2.data(), nullptr);
|
||||
EXPECT_EQ(bcmp(seg3.data(), "123456789", 9), 0);
|
||||
}
|
||||
|
||||
TEST_F(SegmentsTest, data)
|
||||
{
|
||||
Buffer::Segment seg1 = genSeg("123456789");
|
||||
Buffer::Segment seg2 = genSeg("123456789");
|
||||
vector<u_char> vec = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
Buffer::Segment seg3 = Buffer::Segment(std::move(vec));
|
||||
Buffer::Segment seg4 = Buffer::Segment(seg3);
|
||||
EXPECT_EQ(seg1.size(), 9u);
|
||||
EXPECT_EQ(seg3.size(), 9u);
|
||||
EXPECT_EQ(seg4.size(), 9u);
|
||||
EXPECT_EQ(bcmp(seg1.data(), "123456789", 9), 0);
|
||||
EXPECT_EQ(bcmp(seg1.data(), seg2.data(), 9), 0);
|
||||
EXPECT_EQ(bcmp(seg1.data(), seg3.data(), 9), 0);
|
||||
EXPECT_EQ(bcmp(seg4.data(), seg3.data(), 9), 0);
|
||||
}
|
||||
|
||||
TEST_F(SegmentsTest, move_volatile)
|
||||
{
|
||||
Buffer::Segment seg1;
|
||||
{
|
||||
Buffer::Segment seg2 = genSeg("123456789", Buffer::MemoryType::VOLATILE);
|
||||
seg1 = move(seg2);
|
||||
}
|
||||
EXPECT_EQ(bcmp(seg1.data(), "123456789", 9), 0);
|
||||
}
|
||||
93
core/buffers/char_iterator.cc
Executable file
93
core/buffers/char_iterator.cc
Executable file
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
void
|
||||
Buffer::CharIterator::operator++()
|
||||
{
|
||||
if (cur_seg == end_seg) return; // We don't progress past the last segment.
|
||||
offset++;
|
||||
if (offset < size) return;
|
||||
// We've reached the end of the segment, need to move to the next one
|
||||
cur_seg++;
|
||||
offset = 0;
|
||||
if (cur_seg != end_seg) {
|
||||
// New segment, read its values of easy access
|
||||
size = cur_seg->size();
|
||||
ptr = cur_seg->data();
|
||||
} else {
|
||||
// We've reached the end of the buffer, set values accordingly
|
||||
size = 0;
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Buffer::CharIterator::operator+=(uint steps)
|
||||
{
|
||||
while (offset + steps >= cur_seg->size()) { // What we look for is beyond this segment, move to the next one
|
||||
auto skip = cur_seg->size() - offset;
|
||||
steps -= skip;
|
||||
cur_seg++;
|
||||
offset = 0;
|
||||
if (cur_seg == end_seg) {
|
||||
// We've reached the end of the buffer, set values accordingly
|
||||
size = 0;
|
||||
ptr = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
offset += steps;
|
||||
size = cur_seg->size();
|
||||
ptr = cur_seg->data();
|
||||
}
|
||||
|
||||
Buffer::CharIterator
|
||||
Buffer::CharIterator::operator+(uint steps) const
|
||||
{
|
||||
Buffer::CharIterator res = *this;
|
||||
res += steps;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
Buffer::CharIterator::operator==(const CharIterator &other) const
|
||||
{
|
||||
return (cur_seg == other.cur_seg) && (offset == other.offset);
|
||||
}
|
||||
|
||||
const u_char &
|
||||
Buffer::CharIterator::operator*() const
|
||||
{
|
||||
dbgAssert(ptr != nullptr) << "Buffer::CharIterator is not pointing to a real value";
|
||||
return ptr[offset];
|
||||
}
|
||||
|
||||
Buffer::CharIterator::CharIterator(const SegIterator &_cur, const SegIterator &_end, uint _offset)
|
||||
:
|
||||
cur_seg(_cur),
|
||||
end_seg(_end),
|
||||
ptr(_cur->data()),
|
||||
offset(_offset),
|
||||
size(_cur->size())
|
||||
{}
|
||||
|
||||
Buffer::CharIterator::CharIterator(const SegIterator &_end)
|
||||
:
|
||||
cur_seg(_end),
|
||||
end_seg(_end),
|
||||
ptr(nullptr),
|
||||
offset(0),
|
||||
size(0)
|
||||
{}
|
||||
35
core/buffers/data_container.cc
Executable file
35
core/buffers/data_container.cc
Executable file
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
Buffer::DataContainer::DataContainer(std::vector<u_char> &&_vec)
|
||||
:
|
||||
vec(std::move(_vec)),
|
||||
ptr(vec.data()),
|
||||
len(vec.size())
|
||||
{
|
||||
}
|
||||
|
||||
Buffer::DataContainer::DataContainer(const u_char *_ptr, uint _len, MemoryType _type)
|
||||
:
|
||||
len(_len)
|
||||
{
|
||||
if (_type == MemoryType::OWNED) {
|
||||
vec = std::vector<u_char>(_ptr, _ptr + len);
|
||||
ptr = vec.data();
|
||||
} else {
|
||||
ptr = _ptr;
|
||||
is_owned = false;
|
||||
}
|
||||
}
|
||||
162
core/buffers/segment.cc
Executable file
162
core/buffers/segment.cc
Executable file
@@ -0,0 +1,162 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Buffer::Segment::~Segment()
|
||||
{
|
||||
if (type==Volatility::PRIMARY && !data_container.unique()) {
|
||||
// The segment is the PRIMARY holder of the memory, and there are SECONDARY ones of well.
|
||||
// Since the PRIMARY has reached its end-of-life, since the memory is only guaranteed to exist as long as the
|
||||
// primary is alive it needs to be copied so that the other instances could access it later.
|
||||
data_container->takeOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Segment::Segment(const Buffer::Segment &seg)
|
||||
:
|
||||
data_container(seg.data_container),
|
||||
offset(seg.offset),
|
||||
len(seg.len),
|
||||
ptr(seg.ptr)
|
||||
{
|
||||
if (seg.type == Volatility::PRIMARY) {
|
||||
type = Volatility::SECONDARY;
|
||||
is_owned = data_container->checkOnwership();
|
||||
} else {
|
||||
type = seg.type;
|
||||
is_owned = seg.is_owned;
|
||||
}
|
||||
}
|
||||
|
||||
Buffer::Segment::Segment(Segment &&seg)
|
||||
:
|
||||
data_container(move(seg.data_container)),
|
||||
offset(seg.offset),
|
||||
len(seg.len)
|
||||
{
|
||||
if (seg.type == Volatility::PRIMARY) {
|
||||
// The PRIMARY is being moved, meaning it reached its end-of-life. For the current instance to have access to
|
||||
// the memory, the ownership over it must be taken.
|
||||
data_container->takeOwnership();
|
||||
type = Volatility::NONE;
|
||||
is_owned = nullptr;
|
||||
} else {
|
||||
type = seg.type;
|
||||
is_owned = seg.is_owned;
|
||||
}
|
||||
ptr = data_container->data() + offset;
|
||||
|
||||
seg.offset = 0;
|
||||
seg.len = 0;
|
||||
seg.type = Volatility::NONE;
|
||||
seg.ptr = nullptr;
|
||||
seg.is_owned = nullptr;
|
||||
}
|
||||
|
||||
Buffer::Segment &
|
||||
Buffer::Segment::operator=(const Buffer::Segment &seg)
|
||||
{
|
||||
// Copy-assingment is made of two parts:
|
||||
// 1. Destructor logic
|
||||
if (type==Volatility::PRIMARY && !data_container.unique()) {
|
||||
data_container->takeOwnership();
|
||||
}
|
||||
|
||||
// 2. Copy-constructor logic
|
||||
data_container = seg.data_container;
|
||||
offset = seg.offset;
|
||||
len = seg.len;
|
||||
|
||||
ptr = seg.ptr;
|
||||
if (seg.type == Volatility::PRIMARY) {
|
||||
type = Volatility::SECONDARY;
|
||||
is_owned = data_container->checkOnwership();
|
||||
} else {
|
||||
type = seg.type;
|
||||
is_owned = seg.is_owned;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer::Segment &
|
||||
Buffer::Segment::operator=(Segment &&seg)
|
||||
{
|
||||
// Move-assingment is made of two parts:
|
||||
// 1. Destructor logic
|
||||
if (type==Volatility::PRIMARY && !data_container.unique()) {
|
||||
data_container->takeOwnership();
|
||||
}
|
||||
|
||||
// 2. Move-constructor logic
|
||||
data_container = move(seg.data_container);
|
||||
offset = seg.offset;
|
||||
seg.offset = 0;
|
||||
len = seg.len;
|
||||
seg.len = 0;
|
||||
|
||||
if (seg.type == Volatility::PRIMARY) {
|
||||
data_container->takeOwnership();
|
||||
type = Volatility::NONE;
|
||||
is_owned = nullptr;
|
||||
} else {
|
||||
type = seg.type;
|
||||
is_owned = seg.is_owned;
|
||||
}
|
||||
seg.is_owned = nullptr;
|
||||
ptr = data_container->data() + offset;
|
||||
seg.ptr = nullptr;
|
||||
seg.type = Volatility::NONE;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer::Segment::Segment(vector<u_char> &&_vec)
|
||||
:
|
||||
data_container(make_shared<DataContainer>(move(_vec))),
|
||||
offset(0),
|
||||
len(data_container->size()),
|
||||
is_owned(nullptr)
|
||||
{
|
||||
type = Volatility::NONE;
|
||||
ptr = data_container->data();
|
||||
}
|
||||
|
||||
Buffer::Segment::Segment(const u_char *_ptr, uint _len, Buffer::MemoryType _type)
|
||||
:
|
||||
data_container(make_shared<DataContainer>(_ptr, _len, _type)),
|
||||
offset(0),
|
||||
len(_len),
|
||||
is_owned(nullptr)
|
||||
{
|
||||
type = _type==MemoryType::VOLATILE ? Volatility::PRIMARY : Volatility::NONE;
|
||||
ptr = data_container->data();
|
||||
}
|
||||
|
||||
const u_char *
|
||||
Buffer::Segment::data() const
|
||||
{
|
||||
// Check if a copy-in of the memory happened, and if so - recalulate the short circuit.
|
||||
if (is_owned!=nullptr && *is_owned) {
|
||||
// The data has been moved (due to taking ownership), so `ptr` and other members need to be updated.
|
||||
// Since those changes need to happen in a `const` context, the dangerous `const_cast` is used.
|
||||
auto non_const_this = const_cast<Segment *>(this);
|
||||
non_const_this->ptr = data_container->data() + offset;
|
||||
non_const_this->type = Volatility::NONE;
|
||||
non_const_this->is_owned = nullptr;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
9
core/compression/CMakeLists.txt
Executable file
9
core/compression/CMakeLists.txt
Executable file
@@ -0,0 +1,9 @@
|
||||
include_directories(${ng_module_osrc_zlib_path}/include)
|
||||
add_definitions(-DZLIB_CONST)
|
||||
|
||||
add_library(compression_utils SHARED compression_utils.cc)
|
||||
|
||||
add_subdirectory(compression_utils_ut)
|
||||
|
||||
install(TARGETS compression_utils DESTINATION lib)
|
||||
install(TARGETS compression_utils DESTINATION http_transaction_handler_service/lib)
|
||||
384
core/compression/compression_utils.cc
Executable file
384
core/compression/compression_utils.cc
Executable file
@@ -0,0 +1,384 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "compression_utils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using DebugFunction = void(*)(const char *);
|
||||
|
||||
static const int max_debug_level = static_cast<int>(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION);
|
||||
|
||||
static void
|
||||
defaultPrint(const char *debug_message)
|
||||
{
|
||||
cerr << debug_message;
|
||||
};
|
||||
|
||||
class ZlibDebugStream
|
||||
{
|
||||
public:
|
||||
ZlibDebugStream(const CompressionUtilsDebugLevel _debug_level) : debug_level(_debug_level) {}
|
||||
|
||||
~ZlibDebugStream()
|
||||
{
|
||||
ZlibDebugStream::debug_funcs[debug_level](debug_message.str().c_str());
|
||||
|
||||
if (debug_level == CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION) abort();
|
||||
}
|
||||
|
||||
static void
|
||||
resetDebugFunctions()
|
||||
{
|
||||
for (auto &func : debug_funcs) {
|
||||
func = defaultPrint;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setDebugFunction(const CompressionUtilsDebugLevel debug_level, DebugFunction function)
|
||||
{
|
||||
if (static_cast<int>(debug_level) > max_debug_level) return;
|
||||
debug_funcs[static_cast<int>(debug_level)] = function;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ZlibDebugStream & operator<<(const T &message) { debug_message << message; return *this; }
|
||||
|
||||
private:
|
||||
ostringstream debug_message;
|
||||
CompressionUtilsDebugLevel debug_level;
|
||||
|
||||
static array<DebugFunction, max_debug_level + 1> debug_funcs;
|
||||
};
|
||||
|
||||
array<DebugFunction, max_debug_level + 1> ZlibDebugStream::debug_funcs = {
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_TRACE
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_DEBUG
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_INFO
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_WARNING
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR
|
||||
defaultPrint // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION
|
||||
};
|
||||
|
||||
#define zlibDbgError ZlibDebugStream(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR)
|
||||
#define zlibDbgAssertion ZlibDebugStream(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION)
|
||||
|
||||
static const int default_num_window_bits = 15; // Default used by zlib.
|
||||
static const int default_compression_level = Z_DEFAULT_COMPRESSION;
|
||||
static const int default_compression_method = Z_DEFLATED;
|
||||
static const int default_mem_level = 8; // Default recommended in zlib documentation.
|
||||
static const int default_strategy = Z_DEFAULT_STRATEGY;
|
||||
|
||||
static const int zlib_ok_return_value = Z_OK;
|
||||
static const int zlib_stream_done_return_value = Z_STREAM_END;
|
||||
static const int zlib_bad_stream_state_error = Z_STREAM_ERROR;
|
||||
static const int zlib_invalid_data_error = Z_DATA_ERROR;
|
||||
static const int zlib_out_of_memory_error = Z_MEM_ERROR;
|
||||
static const int zlib_version_mismatch_error = Z_VERSION_ERROR;
|
||||
static const int zlib_buf_error = Z_BUF_ERROR;
|
||||
|
||||
static const int zlib_finish_flush = Z_FINISH;
|
||||
static const int zlib_sync_flush = Z_SYNC_FLUSH;
|
||||
static const int zlib_no_flush = Z_NO_FLUSH;
|
||||
|
||||
struct CompressionStream
|
||||
{
|
||||
CompressionStream() { bzero(&stream, sizeof(z_stream)); }
|
||||
~CompressionStream() { fini(); }
|
||||
|
||||
tuple<basic_string<unsigned char>, bool>
|
||||
decompress(const unsigned char *data, uint32_t size)
|
||||
{
|
||||
initInflate();
|
||||
if (state != TYPE::DECOMPRESS) throw runtime_error("Could not start decompression");
|
||||
|
||||
stream.avail_in = size;
|
||||
stream.next_in = data;
|
||||
|
||||
vector<unsigned char> work_space;
|
||||
work_space.reserve(4096);
|
||||
basic_string<unsigned char> res;
|
||||
int retries = 0;
|
||||
|
||||
while (stream.avail_in != 0) {
|
||||
stream.avail_out = work_space.capacity();
|
||||
stream.next_out = work_space.data();
|
||||
|
||||
auto old_total_out = stream.total_out;
|
||||
|
||||
auto inflate_res = inflate(&stream, zlib_no_flush);
|
||||
|
||||
if (inflate_res != Z_OK && inflate_res != Z_STREAM_END) {
|
||||
fini();
|
||||
throw runtime_error("error in 'inflate': " + getZlibError(inflate_res));
|
||||
}
|
||||
|
||||
if (stream.total_out != old_total_out) {
|
||||
res.append(work_space.data(), stream.total_out - old_total_out);
|
||||
} else {
|
||||
++retries;
|
||||
if (retries > 3) {
|
||||
fini();
|
||||
throw runtime_error("No results from inflate more than three times");
|
||||
}
|
||||
}
|
||||
|
||||
if (inflate_res == Z_STREAM_END) {
|
||||
fini();
|
||||
return make_tuple(res, true);
|
||||
}
|
||||
}
|
||||
|
||||
return make_tuple(res, false);
|
||||
}
|
||||
|
||||
basic_string<unsigned char>
|
||||
compress(CompressionType type, const unsigned char *data, uint32_t size, int is_last_chunk)
|
||||
{
|
||||
initDeflate(type);
|
||||
if (state != TYPE::COMPRESS) throw runtime_error("Could not start compression");
|
||||
|
||||
stream.avail_in = size;
|
||||
stream.next_in = data;
|
||||
|
||||
vector<unsigned char> work_space;
|
||||
work_space.reserve(deflateBound(&stream, stream.avail_in));
|
||||
basic_string<unsigned char> res;
|
||||
int retries = 0;
|
||||
|
||||
while (stream.avail_in != 0) {
|
||||
stream.avail_out = work_space.capacity();
|
||||
stream.next_out = work_space.data();
|
||||
|
||||
auto old_total_out = stream.total_out;
|
||||
|
||||
int deflate_res = deflate(&stream, is_last_chunk ? zlib_finish_flush : zlib_sync_flush);
|
||||
|
||||
if (deflate_res != Z_OK && deflate_res != Z_STREAM_END) {
|
||||
fini();
|
||||
throw runtime_error("error in 'deflate': " + getZlibError(deflate_res));
|
||||
}
|
||||
if (stream.total_out != old_total_out) {
|
||||
res.append(work_space.data(), stream.total_out - old_total_out);
|
||||
} else {
|
||||
++retries;
|
||||
if (retries > 3) {
|
||||
fini();
|
||||
throw runtime_error("No results from deflate more than three times");
|
||||
}
|
||||
}
|
||||
if (deflate_res == Z_STREAM_END) {
|
||||
fini();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
initInflate()
|
||||
{
|
||||
if (state != TYPE::UNINITIALIZAED) return;
|
||||
|
||||
auto init_status = inflateInit2(&stream, default_num_window_bits + 32);
|
||||
if (init_status != zlib_ok_return_value) {
|
||||
throw runtime_error(
|
||||
"Failed to initialize decompression stream. Error: " + getZlibError(init_status)
|
||||
);
|
||||
}
|
||||
|
||||
state = TYPE::DECOMPRESS;
|
||||
}
|
||||
|
||||
void
|
||||
initDeflate(CompressionType type)
|
||||
{
|
||||
if (state != TYPE::UNINITIALIZAED) return;
|
||||
|
||||
int num_history_window_bits;
|
||||
switch (type) {
|
||||
case CompressionType::GZIP: {
|
||||
num_history_window_bits = default_num_window_bits + 16;
|
||||
break;
|
||||
}
|
||||
case CompressionType::ZLIB: {
|
||||
num_history_window_bits = default_num_window_bits;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
zlibDbgAssertion
|
||||
<< "Invalid compression type value: "
|
||||
<< static_cast<int>(type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int init_status = deflateInit2(
|
||||
&stream,
|
||||
default_compression_level,
|
||||
default_compression_method,
|
||||
num_history_window_bits,
|
||||
default_mem_level,
|
||||
default_strategy
|
||||
);
|
||||
if (init_status != zlib_ok_return_value) {
|
||||
throw runtime_error(
|
||||
"Failed to initialize compression stream. Error: " + getZlibError(init_status)
|
||||
);
|
||||
}
|
||||
|
||||
state = TYPE::COMPRESS;
|
||||
}
|
||||
|
||||
void
|
||||
fini()
|
||||
{
|
||||
int end_stream_res = zlib_ok_return_value;
|
||||
|
||||
if (state == TYPE::DECOMPRESS) end_stream_res = inflateEnd(&stream);
|
||||
if (state == TYPE::COMPRESS) end_stream_res = deflateEnd(&stream);
|
||||
|
||||
if (end_stream_res != zlib_ok_return_value) {
|
||||
zlibDbgError << "Failed to clean state: " << getZlibError(end_stream_res);
|
||||
}
|
||||
|
||||
state = TYPE::UNINITIALIZAED;
|
||||
}
|
||||
|
||||
string
|
||||
getZlibError(int zlibErrorCode)
|
||||
{
|
||||
switch (zlibErrorCode) {
|
||||
case zlib_buf_error:
|
||||
return "No progress was possible (possibly no more input data or not enough output buffer space)";
|
||||
case zlib_bad_stream_state_error:
|
||||
return "Inconsistent compression stream state";
|
||||
case zlib_invalid_data_error:
|
||||
return "Invalid or corrupted stream data";
|
||||
case zlib_out_of_memory_error:
|
||||
return "Out of memory";
|
||||
case zlib_version_mismatch_error:
|
||||
return "zlib version mismatch";
|
||||
default:
|
||||
return "zlib error occurred. Error code: " + to_string(zlibErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
z_stream stream;
|
||||
enum class TYPE { UNINITIALIZAED, COMPRESS, DECOMPRESS } state = TYPE::UNINITIALIZAED;
|
||||
};
|
||||
|
||||
void
|
||||
resetCompressionDebugFunctionsToStandardError()
|
||||
{
|
||||
ZlibDebugStream::resetDebugFunctions();
|
||||
}
|
||||
|
||||
void
|
||||
setCompressionDebugFunction(const CompressionUtilsDebugLevel debug_level, void (*debug_function)(const char *))
|
||||
{
|
||||
ZlibDebugStream::setDebugFunction(debug_level, debug_function);
|
||||
}
|
||||
|
||||
CompressionStream *
|
||||
initCompressionStream()
|
||||
{
|
||||
return new CompressionStream();
|
||||
}
|
||||
|
||||
void
|
||||
finiCompressionStream(CompressionStream *compression_stream)
|
||||
{
|
||||
delete compression_stream;
|
||||
}
|
||||
|
||||
static unsigned char *
|
||||
duplicateMemory(const basic_string<unsigned char> &str)
|
||||
{
|
||||
auto res = static_cast<unsigned char *>(malloc(str.size()));
|
||||
|
||||
if (res == nullptr) throw bad_alloc();
|
||||
|
||||
memcpy(res, str.data(), str.size());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CompressionResult
|
||||
compressData(
|
||||
CompressionStream *compression_stream,
|
||||
const CompressionType compression_type,
|
||||
const uint32_t data_size,
|
||||
const unsigned char *uncompressed_data,
|
||||
const int is_last_chunk
|
||||
)
|
||||
{
|
||||
CompressionResult result;
|
||||
|
||||
try {
|
||||
if (compression_stream == nullptr) throw invalid_argument("Compression stream is NULL");
|
||||
if (uncompressed_data == nullptr) throw invalid_argument("Data pointer is NULL");
|
||||
|
||||
auto compress = compression_stream->compress(compression_type, uncompressed_data, data_size, is_last_chunk);
|
||||
result.output = duplicateMemory(compress);
|
||||
result.num_output_bytes = compress.size();
|
||||
result.ok = 1;
|
||||
} catch (const exception &e) {
|
||||
zlibDbgError << "Compression failed " << e.what();
|
||||
|
||||
result.ok = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DecompressionResult
|
||||
decompressData(
|
||||
CompressionStream *compression_stream,
|
||||
const uint32_t compressed_data_size,
|
||||
const unsigned char *compressed_data
|
||||
)
|
||||
{
|
||||
DecompressionResult result;
|
||||
|
||||
try {
|
||||
if (compression_stream == nullptr) throw invalid_argument("Compression stream is NULL");
|
||||
if (compressed_data == nullptr) throw invalid_argument("Data pointer is NULL");
|
||||
if (compressed_data_size == 0) throw invalid_argument("Data size is 0");
|
||||
|
||||
auto decompress = compression_stream->decompress(compressed_data, compressed_data_size);
|
||||
result.output = duplicateMemory(get<0>(decompress));
|
||||
result.num_output_bytes = get<0>(decompress).size();
|
||||
result.is_last_chunk = get<1>(decompress);
|
||||
result.ok = 1;
|
||||
} catch (const exception &e) {
|
||||
zlibDbgError << "Decompression failed " << e.what();
|
||||
|
||||
result.ok = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
5
core/compression/compression_utils_ut/CMakeLists.txt
Executable file
5
core/compression/compression_utils_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,5 @@
|
||||
link_directories(${ng_module_osrc_zlib_path}/lib)
|
||||
|
||||
file(COPY test_files DESTINATION .)
|
||||
|
||||
add_unit_test(compression_utils_ut "compression_utils_ut.cc" "compression_utils;-lz")
|
||||
459
core/compression/compression_utils_ut/compression_utils_ut.cc
Executable file
459
core/compression/compression_utils_ut/compression_utils_ut.cc
Executable file
@@ -0,0 +1,459 @@
|
||||
#include <fstream>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "compression_utils.h"
|
||||
#include "buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
using ErrorHook = function<void(const char *)>;
|
||||
|
||||
USE_DEBUG_FLAG(D_COMPRESSION);
|
||||
|
||||
class CompressionUtilsTest : public Test
|
||||
{
|
||||
public:
|
||||
CompressionUtilsTest()
|
||||
{
|
||||
Debug::setUnitTestFlag(D_COMPRESSION, Debug::DebugLevel::ERROR);
|
||||
Debug::setNewDefaultStdout(&capture_debug);
|
||||
|
||||
setCompressionDebugFunction(
|
||||
CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR,
|
||||
[](const char *debug_message) { dbgError(D_COMPRESSION) << debug_message; }
|
||||
);
|
||||
setCompressionDebugFunction(
|
||||
CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION,
|
||||
[](const char *assert_message) { dbgAssert(false) << assert_message; }
|
||||
);
|
||||
}
|
||||
|
||||
~CompressionUtilsTest()
|
||||
{
|
||||
resetOutputStream();
|
||||
}
|
||||
|
||||
void
|
||||
resetOutputStream()
|
||||
{
|
||||
capture_debug.str("");
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
resetCompressionDebugFunctionsToStandardError();
|
||||
}
|
||||
|
||||
string
|
||||
readTestFileContents(const string &file_name)
|
||||
{
|
||||
string file_path = cptestFnameInExeDir(test_files_dir_name + "/" + file_name);
|
||||
ifstream test_string_file(file_path);
|
||||
stringstream string_stream;
|
||||
string_stream << test_string_file.rdbuf();
|
||||
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
compressString(
|
||||
const CompressionType compression_type,
|
||||
const string &uncompressed_string,
|
||||
const bool last_chunk = true,
|
||||
CompressionStream *compression_stream = nullptr
|
||||
)
|
||||
{
|
||||
auto disposable_compression_stream = initCompressionStream();
|
||||
|
||||
CompressionStream *compression_stream_to_use =
|
||||
compression_stream == nullptr ?
|
||||
disposable_compression_stream :
|
||||
compression_stream;
|
||||
|
||||
unsigned char *input_data = reinterpret_cast<unsigned char *>(const_cast<char *>(uncompressed_string.c_str()));
|
||||
CompressionResult compress_data_result = compressData(
|
||||
compression_stream_to_use,
|
||||
compression_type,
|
||||
uncompressed_string.size(),
|
||||
input_data,
|
||||
last_chunk ? 1 : 0
|
||||
);
|
||||
finiCompressionStream(disposable_compression_stream);
|
||||
|
||||
if (compress_data_result.ok == 0) return genError("compressString failed");
|
||||
|
||||
auto compressed_string = string(
|
||||
reinterpret_cast<char *>(compress_data_result.output),
|
||||
compress_data_result.num_output_bytes
|
||||
);
|
||||
free(compress_data_result.output);
|
||||
|
||||
return compressed_string;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
chunkedCompressString(const CompressionType compression_type, const string &uncompressed_string)
|
||||
{
|
||||
vector<string> input_string_chunks = splitIntoChunks(
|
||||
uncompressed_string,
|
||||
uncompressed_string.size() / chunk_size + 1
|
||||
);
|
||||
stringstream compressed_data_ss;
|
||||
auto compression_stream = initCompressionStream();
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < input_string_chunks.size() - 1; curr_chunk_index++) {
|
||||
Maybe<string> compress_string_result = compressString(
|
||||
compression_type,
|
||||
input_string_chunks[curr_chunk_index],
|
||||
false,
|
||||
compression_stream
|
||||
);
|
||||
if (!compress_string_result.ok()) {
|
||||
finiCompressionStream(compression_stream);
|
||||
return genError("chunkedCompressString failed: " + compress_string_result.getErr());
|
||||
}
|
||||
|
||||
compressed_data_ss << compress_string_result.unpack();
|
||||
}
|
||||
|
||||
Maybe<string> compress_string_result = compressString(
|
||||
compression_type,
|
||||
input_string_chunks[input_string_chunks.size() - 1],
|
||||
true,
|
||||
compression_stream
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
|
||||
if (!compress_string_result.ok()) {
|
||||
return genError("chunkedCompressString failed: " + compress_string_result.getErr());
|
||||
}
|
||||
|
||||
compressed_data_ss << compress_string_result.unpack();
|
||||
return compressed_data_ss.str();
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
decompressString(
|
||||
const string &compressed_string,
|
||||
int *is_last_chunk = nullptr,
|
||||
CompressionStream *compression_stream = nullptr
|
||||
)
|
||||
{
|
||||
auto disposable_compression_stream = initCompressionStream();
|
||||
CompressionStream *compression_stream_to_use =
|
||||
compression_stream == nullptr ?
|
||||
disposable_compression_stream :
|
||||
compression_stream;
|
||||
|
||||
unsigned char *compressed_data = reinterpret_cast<unsigned char *>(
|
||||
const_cast<char *>(
|
||||
compressed_string.c_str()
|
||||
)
|
||||
);
|
||||
|
||||
int disposable_is_last_chunk_indicator = 0;
|
||||
int *is_last_chunk_indicator_to_use =
|
||||
is_last_chunk == nullptr ?
|
||||
&disposable_is_last_chunk_indicator :
|
||||
is_last_chunk;
|
||||
|
||||
DecompressionResult decompress_data_result = decompressData(
|
||||
compression_stream_to_use,
|
||||
compressed_string.size(),
|
||||
compressed_data
|
||||
);
|
||||
*is_last_chunk_indicator_to_use = decompress_data_result.is_last_chunk;
|
||||
finiCompressionStream(disposable_compression_stream);
|
||||
if (decompress_data_result.ok == 0) return genError("decompressString failed");
|
||||
|
||||
auto decompressed_string = string(
|
||||
reinterpret_cast<char *>(decompress_data_result.output),
|
||||
decompress_data_result.num_output_bytes
|
||||
);
|
||||
free(decompress_data_result.output);
|
||||
|
||||
return decompressed_string;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
chunkedDecompressString(const string &compressed_string)
|
||||
{
|
||||
auto compression_stream = initCompressionStream();
|
||||
int is_last_chunk = 0;
|
||||
stringstream decompressed_data_ss;
|
||||
|
||||
vector<string> input_string_chunks = splitIntoChunks(
|
||||
compressed_string,
|
||||
compressed_string.size() / chunk_size + 1
|
||||
);
|
||||
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < input_string_chunks.size(); curr_chunk_index++) {
|
||||
Maybe<string> decompress_string_result = decompressString(
|
||||
input_string_chunks[curr_chunk_index],
|
||||
&is_last_chunk,
|
||||
compression_stream
|
||||
);
|
||||
if (!decompress_string_result.ok()) {
|
||||
finiCompressionStream(compression_stream);
|
||||
return genError("chunkedDecompress failed: " + decompress_string_result.getErr());
|
||||
}
|
||||
|
||||
decompressed_data_ss << decompress_string_result.unpack();
|
||||
}
|
||||
|
||||
finiCompressionStream(compression_stream);
|
||||
return decompressed_data_ss.str();
|
||||
}
|
||||
|
||||
bool
|
||||
performCompressionNullPointerTest()
|
||||
{
|
||||
static const vector<int> possible_last_chunk_values = { 0, 1 };
|
||||
string compress_test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
string decompress_test_string = readTestFileContents(chunk_sized_gzip_file_name);
|
||||
|
||||
for (CompressionType single_compression_type : compression_types) {
|
||||
for (int single_possible_last_chunk_value : possible_last_chunk_values) {
|
||||
CompressionResult result = compressData(
|
||||
nullptr,
|
||||
single_compression_type,
|
||||
compress_test_string.size(),
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(compress_test_string.c_str())),
|
||||
single_possible_last_chunk_value
|
||||
);
|
||||
|
||||
if (result.ok) return false;
|
||||
}
|
||||
}
|
||||
|
||||
DecompressionResult result = decompressData(
|
||||
nullptr,
|
||||
decompress_test_string.size(),
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(decompress_test_string.c_str()))
|
||||
);
|
||||
|
||||
if (result.ok) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<string>
|
||||
splitIntoChunks(const string &data, const uint32_t num_data_chunks)
|
||||
{
|
||||
vector<string> data_chunks;
|
||||
|
||||
uint32_t num_data_chunks_to_use = min(static_cast<uint32_t>(data.size()), num_data_chunks);
|
||||
if (num_data_chunks_to_use == 1) return { data };
|
||||
|
||||
uint32_t chunk_size = data.size() / num_data_chunks;
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < num_data_chunks_to_use - 1; curr_chunk_index++) {
|
||||
data_chunks.push_back(string(data.c_str() + curr_chunk_index * chunk_size, chunk_size));
|
||||
}
|
||||
|
||||
uint32_t accumulated_chunks_size = (num_data_chunks_to_use - 1) * chunk_size;
|
||||
data_chunks.push_back(string(data.c_str() + accumulated_chunks_size, data.size() - accumulated_chunks_size));
|
||||
|
||||
return data_chunks;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
calcCompressedDataSizeBound(const uint32_t compressed_data_size)
|
||||
{
|
||||
return 2 * compressed_data_size;
|
||||
}
|
||||
|
||||
ostringstream capture_debug;
|
||||
|
||||
const string simple_test_string = "Test data for compression utilities library";
|
||||
const string chunk_sized_string_file_name = "chunk_sized_string";
|
||||
const string chunk_sized_gzip_file_name = "chunk_sized_compressed_file.gz";
|
||||
const string chunk_sized_zlib_file_name = "chunk_sized_compressed_file.zz";
|
||||
const string multi_chunk_sized_string_file_name = "multiple_chunk_sized_string";
|
||||
const string multi_chunk_sized_gzip_file_name = "multiple_chunk_sized_compressed_file.gz";
|
||||
const string multi_chunk_sized_zlib_file_name = "multiple_chunk_sized_compressed_file.zz";
|
||||
const vector<string> chunk_sized_compressed_files = { chunk_sized_gzip_file_name, chunk_sized_zlib_file_name };
|
||||
const vector<string> multi_chunk_sized_compressed_files = {
|
||||
multi_chunk_sized_gzip_file_name,
|
||||
multi_chunk_sized_zlib_file_name
|
||||
};
|
||||
|
||||
const vector<CompressionType> compression_types = { CompressionType::GZIP, CompressionType::ZLIB };
|
||||
const uint32_t chunk_size = 32768;
|
||||
|
||||
private:
|
||||
const string test_files_dir_name = "test_files";
|
||||
};
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressAndDecompressSimpleString)
|
||||
{
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> compressed_string_maybe = compressString(
|
||||
single_compression_type,
|
||||
simple_test_string
|
||||
);
|
||||
EXPECT_TRUE(compressed_string_maybe.ok());
|
||||
|
||||
Maybe<string> decompressed_string_maybe = decompressString(compressed_string_maybe.unpack());
|
||||
EXPECT_TRUE(decompressed_string_maybe.ok());
|
||||
|
||||
EXPECT_EQ(simple_test_string, decompressed_string_maybe.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressAndDecompressChunkSizedString)
|
||||
{
|
||||
string test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> compressed_string_maybe = compressString(
|
||||
single_compression_type,
|
||||
test_string
|
||||
);
|
||||
EXPECT_TRUE(compressed_string_maybe.ok());
|
||||
|
||||
Maybe<string> decompressed_string_maybe = decompressString(compressed_string_maybe.unpack());
|
||||
EXPECT_TRUE(decompressed_string_maybe.ok());
|
||||
|
||||
EXPECT_EQ(test_string, decompressed_string_maybe.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressMultipleChunkSizedStringAndDecompress)
|
||||
{
|
||||
string test_string = readTestFileContents(multi_chunk_sized_string_file_name);
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> chunked_compress_result = chunkedCompressString(single_compression_type, test_string);
|
||||
EXPECT_TRUE(chunked_compress_result.ok());
|
||||
|
||||
Maybe<string> chunked_decompress_result = chunkedDecompressString(chunked_compress_result.unpack());
|
||||
EXPECT_TRUE(chunked_decompress_result.ok());
|
||||
|
||||
EXPECT_EQ(chunked_decompress_result.unpack(), test_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressChunkSizedCompressedFile)
|
||||
{
|
||||
for (const auto &single_compressed_file_name : chunk_sized_compressed_files) {
|
||||
string test_string = readTestFileContents(single_compressed_file_name);
|
||||
|
||||
string expected_decompressed_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
Maybe<string> decompressed_string_result = decompressString(test_string);
|
||||
EXPECT_TRUE(decompressed_string_result.ok());
|
||||
EXPECT_EQ(decompressed_string_result.unpack(), expected_decompressed_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressMultipleChunkSizedCompressedFile)
|
||||
{
|
||||
for (const auto &single_compressed_file_name : multi_chunk_sized_compressed_files) {
|
||||
string test_string = readTestFileContents(single_compressed_file_name);
|
||||
|
||||
Maybe<string> chunked_decompress_result = chunkedDecompressString(test_string);
|
||||
EXPECT_TRUE(chunked_decompress_result.ok());
|
||||
|
||||
string expected_decompressed_string = readTestFileContents(multi_chunk_sized_string_file_name);
|
||||
EXPECT_EQ(chunked_decompress_result.unpack(), expected_decompressed_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, TestEmptyBuffer)
|
||||
{
|
||||
for (CompressionType compression_type : compression_types) {
|
||||
auto compression_stream = initCompressionStream();
|
||||
stringstream compressed_stream;
|
||||
|
||||
Maybe<string> compressed_string = compressString(
|
||||
compression_type,
|
||||
simple_test_string,
|
||||
false,
|
||||
compression_stream
|
||||
);
|
||||
EXPECT_TRUE(compressed_string.ok());
|
||||
compressed_stream << compressed_string.unpack();
|
||||
|
||||
compressed_string = compressString(
|
||||
compression_type,
|
||||
"",
|
||||
true,
|
||||
compression_stream
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
EXPECT_TRUE(compressed_string.ok());
|
||||
compressed_stream << compressed_string.unpack();
|
||||
|
||||
Buffer compressed_buffer(compressed_stream.str());
|
||||
|
||||
int is_last_chunk;
|
||||
auto decompression_stream = initCompressionStream();
|
||||
|
||||
Maybe<string> decompressed_string = decompressString(
|
||||
compressed_stream.str(),
|
||||
&is_last_chunk,
|
||||
decompression_stream
|
||||
);
|
||||
|
||||
EXPECT_TRUE(decompressed_string.ok());
|
||||
EXPECT_EQ(decompressed_string.unpack(), simple_test_string);
|
||||
finiCompressionStream(decompression_stream);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressionStreamNullPointer)
|
||||
{
|
||||
EXPECT_TRUE(performCompressionNullPointerTest());
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("Compression failed Compression stream is NULL")
|
||||
);
|
||||
|
||||
resetOutputStream();
|
||||
EXPECT_TRUE(performCompressionNullPointerTest());
|
||||
EXPECT_EQ(capture_debug.str(), string());
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, InputDataBufferNullPointer)
|
||||
{
|
||||
static const vector<int> possible_last_chunk_values = { 0, 1 };
|
||||
string compress_test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
auto compression_stream = initCompressionStream();
|
||||
|
||||
for (CompressionType single_compression_type : compression_types) {
|
||||
for (int single_possible_last_chunk_value : possible_last_chunk_values) {
|
||||
CompressionResult result = compressData(
|
||||
compression_stream,
|
||||
single_compression_type,
|
||||
compress_test_string.size(),
|
||||
nullptr,
|
||||
single_possible_last_chunk_value
|
||||
);
|
||||
|
||||
EXPECT_EQ(result.ok, 0);
|
||||
}
|
||||
}
|
||||
|
||||
string decompress_test_string = readTestFileContents(chunk_sized_gzip_file_name);
|
||||
finiCompressionStream(compression_stream);
|
||||
compression_stream = initCompressionStream();
|
||||
|
||||
DecompressionResult result = decompressData(
|
||||
compression_stream,
|
||||
decompress_test_string.size(),
|
||||
nullptr
|
||||
);
|
||||
|
||||
EXPECT_EQ(result.ok, 0);
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("Compression failed Data pointer is NULL")
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressPlainText)
|
||||
{
|
||||
Maybe<string> decompress_string_result = decompressString(simple_test_string);
|
||||
|
||||
EXPECT_FALSE(decompress_string_result.ok());
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("error in 'inflate': Invalid or corrupted stream data")
|
||||
);
|
||||
}
|
||||
Binary file not shown.
BIN
core/compression/compression_utils_ut/test_files/chunk_sized_compressed_file.zz
Executable file
BIN
core/compression/compression_utils_ut/test_files/chunk_sized_compressed_file.zz
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/compression/compression_utils_ut/test_files/multiple_chunk_sized_string
Executable file
BIN
core/compression/compression_utils_ut/test_files/multiple_chunk_sized_string
Executable file
Binary file not shown.
Binary file not shown.
4
core/config/CMakeLists.txt
Normal file
4
core/config/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
add_library(config config.cc config_specific.cc config_globals.cc)
|
||||
target_link_libraries(config agent_core_utilities)
|
||||
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
850
core/config/config.cc
Normal file
850
core/config/config.cc
Normal file
@@ -0,0 +1,850 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <boost/regex.hpp>
|
||||
#include "agent_core_utilities.h"
|
||||
|
||||
#include "cereal/archives/json.hpp"
|
||||
|
||||
#include "debug.h"
|
||||
#include "cereal/external/rapidjson/error/en.h"
|
||||
#include "include/profile_settings.h"
|
||||
#include "enum_range.h"
|
||||
#include "rest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace cereal;
|
||||
using namespace Config;
|
||||
|
||||
USE_DEBUG_FLAG(D_CONFIG);
|
||||
|
||||
AgentProfileSettings AgentProfileSettings::default_profile_settings = AgentProfileSettings();
|
||||
|
||||
class registerExpectedConfigUpdates : public ClientRest
|
||||
{
|
||||
public:
|
||||
C2S_PARAM(string, service_name);
|
||||
C2S_OPTIONAL_PARAM(string, service_id);
|
||||
C2S_PARAM(int, service_listening_port);
|
||||
C2S_PARAM(vector<string>, expected_configurations);
|
||||
S2C_PARAM(bool, status);
|
||||
};
|
||||
|
||||
class LoadNewConfigurationStatus : public ClientRest
|
||||
{
|
||||
public:
|
||||
LoadNewConfigurationStatus(uint _id, bool _error, bool end) : id(_id), error(_error), finished(end) {}
|
||||
|
||||
void setError(const string &error) { error_message = error; }
|
||||
|
||||
private:
|
||||
C2S_PARAM(int, id);
|
||||
C2S_PARAM(bool, error);
|
||||
C2S_PARAM(bool, finished);
|
||||
C2S_OPTIONAL_PARAM(string, error_message);
|
||||
};
|
||||
|
||||
class LoadNewConfiguration : public ServerRest
|
||||
{
|
||||
public:
|
||||
void
|
||||
doCall() override
|
||||
{
|
||||
auto i_config = Singleton::Consume<I_Config>::from<ConfigComponent>();
|
||||
|
||||
I_Config::AsyncLoadConfigStatus load_config_staus = i_config->reloadConfiguration(policy_version, true, id);
|
||||
finished = load_config_staus == I_Config::AsyncLoadConfigStatus::InProgress;
|
||||
error = load_config_staus == I_Config::AsyncLoadConfigStatus::Error;
|
||||
if (!finished) {
|
||||
error_message = "Reload already in progress - can't start another one";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BOTH_PARAM(int, id);
|
||||
S2C_PARAM(bool, error);
|
||||
S2C_PARAM(bool, finished);
|
||||
S2C_OPTIONAL_PARAM(string, error_message);
|
||||
C2S_PARAM(string, policy_version);
|
||||
};
|
||||
|
||||
class ConfigComponent::Impl : public Singleton::Provide<I_Config>::From<ConfigComponent>
|
||||
{
|
||||
using PerContextValue = vector<pair<shared_ptr<EnvironmentEvaluator<bool>>, TypeWrapper>>;
|
||||
|
||||
public:
|
||||
void preload();
|
||||
void init();
|
||||
|
||||
const TypeWrapper & getConfiguration(const vector<string> &paths) const override;
|
||||
PerContextValue getAllConfiguration(const std::vector<std::string> &paths) const;
|
||||
const TypeWrapper & getResource(const vector<string> &paths) const override;
|
||||
const TypeWrapper & getSetting(const vector<string> &paths) const override;
|
||||
const string & getProfileAgentSetting(const string &setting_name) const override;
|
||||
vector<string> getProfileAgentSettings(const string ®ex) const override;
|
||||
|
||||
const string & getConfigurationFlag(const string &flag_name) const override;
|
||||
const string & getFilesystemPathConfig() const override;
|
||||
const string & getLogFilesPathConfig() const override;
|
||||
|
||||
string getPolicyConfigPath(const string &name, ConfigFileType type, const string &tenant = "") const override;
|
||||
|
||||
bool setConfiguration(TypeWrapper &&value, const std::vector<std::string> &paths) override;
|
||||
bool setResource(TypeWrapper &&value, const std::vector<std::string> &paths) override;
|
||||
bool setSetting(TypeWrapper &&value, const std::vector<std::string> &paths) override;
|
||||
|
||||
void registerExpectedConfigFile(const string &file_name, ConfigFileType type) override;
|
||||
void registerExpectedConfiguration(unique_ptr<GenericConfig<true>> &&config) override;
|
||||
void registerExpectedResource(unique_ptr<GenericConfig<false>> &&config) override;
|
||||
void registerExpectedSetting(unique_ptr<GenericConfig<false>> &&config) override;
|
||||
|
||||
|
||||
bool loadConfiguration(istream &json_contents) override;
|
||||
bool loadConfiguration(const vector<string> &configuration_flags) override;
|
||||
AsyncLoadConfigStatus reloadConfiguration(const string &version, bool is_async, uint id) override;
|
||||
bool saveConfiguration(ostream &) const override { return false; }
|
||||
|
||||
void registerConfigPrepareCb(ConfigCb) override;
|
||||
void registerConfigLoadCb(ConfigCb) override;
|
||||
void registerConfigAbortCb(ConfigCb) override;
|
||||
|
||||
private:
|
||||
void clearOldTenants();
|
||||
bool isTenantActive(const string &name) const;
|
||||
void periodicRegistrationRefresh();
|
||||
|
||||
bool loadConfiguration(vector<shared_ptr<JSONInputArchive>> &file_archives, bool is_async);
|
||||
bool commitSuccess();
|
||||
bool commitFailure(const string &error);
|
||||
bool reloadConfigurationImpl(const string &version, bool is_async);
|
||||
void reloadConfigurationContinuesWrapper(const string &version, uint id);
|
||||
|
||||
string
|
||||
getActiveTenant() const
|
||||
{
|
||||
auto active_id = Singleton::Consume<I_Environment>::by<ConfigComponent>()->get<string>("ActiveTenantId");
|
||||
|
||||
return active_id.ok() ? *active_id : default_tenant_id;
|
||||
}
|
||||
|
||||
bool
|
||||
sendOrchestatorConfMsg(int env_listening_port)
|
||||
{
|
||||
registerExpectedConfigUpdates config_updates;
|
||||
|
||||
config_updates.service_name = executable_name;
|
||||
config_updates.service_listening_port = env_listening_port;
|
||||
|
||||
|
||||
if (Singleton::exists<I_InstanceAwareness>()) {
|
||||
auto instance_awareness = Singleton::Consume<I_InstanceAwareness>::by<ConfigComponent>();
|
||||
if (instance_awareness->getUniqueID().ok()) {
|
||||
config_updates.service_id = instance_awareness->getUniqueID().unpack();
|
||||
}
|
||||
}
|
||||
|
||||
vector<string> files;
|
||||
files.reserve(expected_configuration_files.size());
|
||||
for (const auto &conf : expected_configuration_files) {
|
||||
files.push_back(conf.first);
|
||||
}
|
||||
config_updates.expected_configurations = move(files);
|
||||
|
||||
I_Messaging *messaging = Singleton::Consume<I_Messaging>::by<ConfigComponent>();
|
||||
::Flags<MessageConnConfig> conn_flags;
|
||||
conn_flags.setFlag(MessageConnConfig::ONE_TIME_CONN);
|
||||
bool is_success = messaging->sendObject(
|
||||
config_updates,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7777, // primary Orchestrator's port
|
||||
conn_flags,
|
||||
"/set-nano-service-config"
|
||||
);
|
||||
if (!is_success) {
|
||||
is_success = messaging->sendObject(
|
||||
config_updates,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7778, // secondary Orchestrator's port
|
||||
conn_flags,
|
||||
"/set-nano-service-config"
|
||||
);
|
||||
}
|
||||
return is_success && config_updates.status.get();
|
||||
}
|
||||
|
||||
void
|
||||
reloadFileSystemPaths()
|
||||
{
|
||||
auto &alternative_conf_path = getConfigurationFlag("configDirectoryPath");
|
||||
if (alternative_conf_path != "") {
|
||||
config_directory_path = alternative_conf_path;
|
||||
} else {
|
||||
filesystem_prefix = getConfigurationFlag("filesystem_path") != "" ?
|
||||
getConfigurationFlag("filesystem_path") :
|
||||
"/etc/cp";
|
||||
log_files_prefix = getConfigurationFlag("log_files_path") != "" ?
|
||||
getConfigurationFlag("log_files_path") :
|
||||
"/var/log";
|
||||
config_directory_path = filesystem_prefix + default_config_directory_path;
|
||||
}
|
||||
dbgTrace(D_CONFIG) << "File system path reloaded: " << config_directory_path;
|
||||
}
|
||||
|
||||
void
|
||||
sendOrchestatorReloadStatusMsg(const LoadNewConfigurationStatus &status)
|
||||
{
|
||||
I_Messaging *messaging = Singleton::Consume<I_Messaging>::by<ConfigComponent>();
|
||||
::Flags<MessageConnConfig> conn_flags;
|
||||
conn_flags.setFlag(MessageConnConfig::ONE_TIME_CONN);
|
||||
bool is_success = messaging->sendNoReplyObject(
|
||||
status,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7777, // primary Orchestrator's port
|
||||
conn_flags,
|
||||
"/set-reconf-status"
|
||||
);
|
||||
if (!is_success) {
|
||||
messaging->sendNoReplyObject(
|
||||
status,
|
||||
I_Messaging::Method::POST,
|
||||
"127.0.0.1",
|
||||
7778, // secondary Orchestrator's port
|
||||
conn_flags,
|
||||
"/set-reconf-status"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unordered_map<string, map<vector<string>, PerContextValue>> configuration_nodes;
|
||||
unordered_map<string, map<vector<string>, TypeWrapper>> settings_nodes;
|
||||
map<string, string> profile_settings;
|
||||
unordered_map<string, string> config_flags;
|
||||
|
||||
map<vector<string>, TypeWrapper> new_resource_nodes;
|
||||
unordered_map<string, map<vector<string>, PerContextValue>> new_configuration_nodes;
|
||||
unordered_map<string, map<vector<string>, TypeWrapper>> new_settings_nodes;
|
||||
unordered_map<string, string> new_config_flags;
|
||||
|
||||
set<unique_ptr<GenericConfig<true>>> expected_configs;
|
||||
set<unique_ptr<GenericConfig<false>>> expected_resources;
|
||||
set<unique_ptr<GenericConfig<false>>> expected_settings;
|
||||
map<string, set<ConfigFileType>> expected_configuration_files;
|
||||
set<string> config_file_paths;
|
||||
|
||||
I_TenantManager *tenant_mananger = nullptr;
|
||||
|
||||
vector<ConfigCb> configuration_prepare_cbs;
|
||||
vector<ConfigCb> configuration_commit_cbs;
|
||||
vector<ConfigCb> configuration_abort_cbs;
|
||||
|
||||
bool is_continuous_report = false;
|
||||
const string default_tenant_id = "default_tenant_id_value";
|
||||
string executable_name = "";
|
||||
string filesystem_prefix = "/etc/cp";
|
||||
string log_files_prefix = "/var/log";
|
||||
string default_config_directory_path = "/conf/";
|
||||
string config_directory_path = "";
|
||||
|
||||
TypeWrapper empty;
|
||||
};
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::preload()
|
||||
{
|
||||
I_Environment *environment = Singleton::Consume<I_Environment>::by<ConfigComponent>();
|
||||
auto executable = environment->get<string>("Executable Name");
|
||||
if (!executable.ok() || *executable == "") {
|
||||
dbgWarning(D_CONFIG)
|
||||
<< "Could not load nano service's settings since \"Executable Name\" in not found in the environment";
|
||||
return;
|
||||
}
|
||||
|
||||
executable_name = *executable;
|
||||
auto file_path_end = executable_name.find_last_of("/");
|
||||
if (file_path_end != string::npos) {
|
||||
executable_name = executable_name.substr(file_path_end + 1);
|
||||
}
|
||||
auto file_sufix_start = executable_name.find_first_of(".");
|
||||
if (file_sufix_start != string::npos) {
|
||||
executable_name = executable_name.substr(0, file_sufix_start);
|
||||
}
|
||||
|
||||
config_file_paths.insert(executable_name + "-conf.json");
|
||||
config_file_paths.insert(executable_name + "-debug-conf.json");
|
||||
config_file_paths.insert("settings.json");
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::init()
|
||||
{
|
||||
reloadFileSystemPaths();
|
||||
tenant_mananger = Singleton::Consume<I_TenantManager>::by<ConfigComponent>();
|
||||
|
||||
if (!Singleton::exists<I_MainLoop>()) return;
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::by<ConfigComponent>();
|
||||
|
||||
if (executable_name != "cp-nano-orchestration") {
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
[this] () { periodicRegistrationRefresh(); },
|
||||
"Configuration update registration",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
mainloop->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
tenant_mananger->getTimeoutVal(),
|
||||
[this] () { clearOldTenants(); },
|
||||
"Config comp old tenant cleanup"
|
||||
);
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
checkContext(const shared_ptr<EnvironmentEvaluator<bool>> &ctx)
|
||||
{
|
||||
if (ctx == nullptr) return true;
|
||||
auto res = ctx->evalVariable();
|
||||
return res.ok() && *res;
|
||||
}
|
||||
|
||||
const TypeWrapper &
|
||||
ConfigComponent::Impl::getConfiguration(const vector<string> &paths) const
|
||||
{
|
||||
auto curr_configs = configuration_nodes.find(getActiveTenant());
|
||||
|
||||
if (curr_configs != configuration_nodes.end()) {
|
||||
auto requested_config = curr_configs->second.find(paths);
|
||||
if (requested_config != curr_configs->second.end()) {
|
||||
for (auto &value : requested_config->second) {
|
||||
if (checkContext(value.first)) return value.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto global_config = configuration_nodes.find(default_tenant_id);
|
||||
if (global_config != configuration_nodes.end()) {
|
||||
auto requested_config = global_config->second.find(paths);
|
||||
if (requested_config != global_config->second.end()) {
|
||||
for (auto &value : requested_config->second) {
|
||||
if (checkContext(value.first)) return value.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
vector<pair<shared_ptr<EnvironmentEvaluator<bool>>, TypeWrapper>>
|
||||
ConfigComponent::Impl::getAllConfiguration(const vector<string> &paths) const
|
||||
{
|
||||
auto curr_configs = configuration_nodes.find(getActiveTenant());
|
||||
|
||||
if (curr_configs != configuration_nodes.end()) {
|
||||
auto requested_config = curr_configs->second.find(paths);
|
||||
if (requested_config != curr_configs->second.end()) return requested_config->second;
|
||||
}
|
||||
|
||||
auto global_config = configuration_nodes.find(default_tenant_id);
|
||||
if (global_config != configuration_nodes.end()) {
|
||||
auto requested_config = global_config->second.find(paths);
|
||||
if (requested_config != global_config->second.end()) return requested_config->second;
|
||||
}
|
||||
|
||||
return vector<pair<shared_ptr<EnvironmentEvaluator<bool>>, TypeWrapper>>();
|
||||
}
|
||||
|
||||
const TypeWrapper &
|
||||
ConfigComponent::Impl::getResource(const vector<string> &paths) const
|
||||
{
|
||||
auto requested_resource = new_resource_nodes.find(paths);
|
||||
if (requested_resource != new_resource_nodes.end()) return requested_resource->second;
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
const TypeWrapper &
|
||||
ConfigComponent::Impl::getSetting(const vector<string> &paths) const
|
||||
{
|
||||
auto curr_configs = settings_nodes.find(getActiveTenant());
|
||||
if (curr_configs != settings_nodes.end()) {
|
||||
auto requested_config = curr_configs->second.find(paths);
|
||||
if (requested_config != curr_configs->second.end()) return requested_config->second;
|
||||
}
|
||||
|
||||
auto global_config = settings_nodes.find(default_tenant_id);
|
||||
if (global_config != settings_nodes.end()) {
|
||||
auto requested_config = global_config->second.find(paths);
|
||||
if (requested_config != global_config->second.end()) return requested_config->second;
|
||||
}
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
const string &
|
||||
ConfigComponent::Impl::getProfileAgentSetting(const string &setting_name) const
|
||||
{
|
||||
auto setting_raw_val = profile_settings.find(setting_name);
|
||||
if (setting_raw_val != profile_settings.end()) return setting_raw_val->second;
|
||||
|
||||
const static string not_found = "";
|
||||
return not_found;
|
||||
}
|
||||
|
||||
const string &
|
||||
ConfigComponent::Impl::getConfigurationFlag(const string &flag_name) const
|
||||
{
|
||||
auto flag = new_config_flags.find(flag_name);
|
||||
if (flag != new_config_flags.end()) return flag->second;
|
||||
|
||||
flag = config_flags.find(flag_name);
|
||||
if (flag != config_flags.end()) return flag->second;
|
||||
|
||||
const static string not_found = "";
|
||||
return not_found;
|
||||
}
|
||||
|
||||
const string &
|
||||
ConfigComponent::Impl::getFilesystemPathConfig() const
|
||||
{
|
||||
dbgTrace(D_CONFIG) << "config get filesystem: " << filesystem_prefix;
|
||||
return filesystem_prefix;
|
||||
}
|
||||
|
||||
const string &
|
||||
ConfigComponent::Impl::getLogFilesPathConfig() const
|
||||
{
|
||||
dbgTrace(D_CONFIG) << "config get log_files_prefix: " << log_files_prefix;
|
||||
return log_files_prefix;
|
||||
}
|
||||
|
||||
string
|
||||
ConfigComponent::Impl::getPolicyConfigPath(const string &config_name, ConfigFileType type, const string &tenant) const
|
||||
{
|
||||
static const string policy_suffix = ".policy";
|
||||
static const string data_suffix = ".data";
|
||||
static const string tenant_prefix = "tenant_";
|
||||
|
||||
string base_path =
|
||||
getConfigurationWithDefault(config_directory_path, "Config Component", "configuration path") +
|
||||
(tenant.empty() ? "" : tenant_prefix + tenant + "/");
|
||||
|
||||
switch (type) {
|
||||
case ConfigFileType::Data: return base_path + "data/" + config_name + data_suffix;
|
||||
case ConfigFileType::RawData: return base_path + "data/" + config_name + data_suffix;
|
||||
case ConfigFileType::Policy: return base_path + config_name + "/" + config_name + policy_suffix;
|
||||
case ConfigFileType::COUNT: break;
|
||||
}
|
||||
|
||||
dbgError(D_CONFIG) << "Received illegal configuration file type " << static_cast<uint>(type);
|
||||
return "";
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::setConfiguration(TypeWrapper &&value, const vector<string> &paths)
|
||||
{
|
||||
for (auto &tennant : configuration_nodes) {
|
||||
tennant.second.erase(paths);
|
||||
}
|
||||
|
||||
PerContextValue value_vec;
|
||||
value_vec.emplace_back(nullptr, move(value));
|
||||
configuration_nodes[default_tenant_id][paths] = move(value_vec);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::setResource(TypeWrapper &&value, const vector<string> &paths)
|
||||
{
|
||||
new_resource_nodes[paths] = move(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::setSetting(TypeWrapper &&value, const vector<string> &paths)
|
||||
{
|
||||
settings_nodes[default_tenant_id][paths] = move(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<string>
|
||||
ConfigComponent::Impl::getProfileAgentSettings(const string ®ex) const
|
||||
{
|
||||
vector<string> setting_raw_values;
|
||||
boost::regex reg(regex);
|
||||
for (auto &setting : profile_settings) {
|
||||
if (NGEN::Regex::regexMatch(__FILE__, __LINE__, setting.first, reg)) {
|
||||
setting_raw_values.push_back(setting.second);
|
||||
}
|
||||
}
|
||||
|
||||
return setting_raw_values;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerExpectedConfigFile(const string &config_name, ConfigFileType type)
|
||||
{
|
||||
expected_configuration_files[config_name].insert(type);
|
||||
if (type != ConfigFileType::RawData) config_file_paths.insert(getPolicyConfigPath(config_name, type));
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerExpectedConfiguration(unique_ptr<GenericConfig<true>> &&expected_config)
|
||||
{
|
||||
expected_configs.insert(move(expected_config));
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerExpectedResource(unique_ptr<GenericConfig<false>> &&expected_config)
|
||||
{
|
||||
expected_resources.insert(move(expected_config));
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerExpectedSetting(unique_ptr<GenericConfig<false>> &&expected_config)
|
||||
{
|
||||
expected_settings.insert(move(expected_config));
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::loadConfiguration(istream &stream)
|
||||
{
|
||||
vector<shared_ptr<JSONInputArchive>> archive;
|
||||
try {
|
||||
archive.emplace_back(make_shared<JSONInputArchive>(stream));
|
||||
} catch (const cereal::Exception &e) {
|
||||
dbgError(D_CONFIG) << "Failed to load stream: " << e.what();
|
||||
return false;
|
||||
}
|
||||
return loadConfiguration(archive, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::loadConfiguration(const vector<string> &flags)
|
||||
{
|
||||
for (auto &flag : flags) {
|
||||
if (flag.substr(0, 2) == "--") {
|
||||
auto equal_place = flag.find_first_of('=');
|
||||
if (equal_place == string::npos) continue;
|
||||
dbgDebug(D_CONFIG)
|
||||
<< "Adding "
|
||||
<< flag.substr(2, equal_place - 2)
|
||||
<< "='"
|
||||
<< flag.substr(equal_place +1)
|
||||
<< "'";
|
||||
new_config_flags.emplace(flag.substr(2, equal_place - 2), flag.substr(equal_place +1));
|
||||
} else {
|
||||
dbgInfo(D_CONFIG) << "ignoring an illegal configuration argument. Argument: " << flag;
|
||||
}
|
||||
}
|
||||
reloadFileSystemPaths();
|
||||
|
||||
auto &alternative_conf_path = getConfigurationFlag("configDirectoryPath");
|
||||
if (alternative_conf_path != "") {
|
||||
config_directory_path = alternative_conf_path;
|
||||
dbgTrace(D_CONFIG) << "File system path reloaded from configuration flag: " << config_directory_path;
|
||||
}
|
||||
|
||||
auto res = reloadConfiguration("", false, 0) == I_Config::AsyncLoadConfigStatus::Success;
|
||||
|
||||
if (res && !new_config_flags.empty()) {
|
||||
config_flags = move(new_config_flags);
|
||||
} else {
|
||||
new_config_flags.clear();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
I_Config::AsyncLoadConfigStatus
|
||||
ConfigComponent::Impl::reloadConfiguration(const string &version, bool is_async, uint id)
|
||||
{
|
||||
if (is_continuous_report) {
|
||||
dbgWarning(D_CONFIG) << "Cannot start another continuous reload while another is running.";
|
||||
return AsyncLoadConfigStatus::InProgress;
|
||||
}
|
||||
|
||||
if (!is_async) {
|
||||
bool res = reloadConfigurationImpl(version, false);
|
||||
return res ? I_Config::AsyncLoadConfigStatus::Success : I_Config::AsyncLoadConfigStatus::Error;
|
||||
}
|
||||
|
||||
is_continuous_report = true;
|
||||
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::by<ConfigComponent>();
|
||||
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
[=] () { reloadConfigurationContinuesWrapper(version, id); },
|
||||
"A-Synchronize reload configuraion"
|
||||
);
|
||||
|
||||
return AsyncLoadConfigStatus::Success;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerConfigPrepareCb(ConfigCb cb)
|
||||
{
|
||||
configuration_prepare_cbs.push_back(cb);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerConfigLoadCb(ConfigCb cb)
|
||||
{
|
||||
configuration_commit_cbs.push_back(cb);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::registerConfigAbortCb(ConfigCb cb)
|
||||
{
|
||||
configuration_abort_cbs.push_back(cb);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::clearOldTenants()
|
||||
{
|
||||
for (
|
||||
auto iter = configuration_nodes.begin();
|
||||
iter != configuration_nodes.end();
|
||||
!isTenantActive(iter->first) ? iter = configuration_nodes.erase(iter) : ++iter
|
||||
);
|
||||
|
||||
for (
|
||||
auto iter = settings_nodes.begin();
|
||||
iter != settings_nodes.end();
|
||||
!isTenantActive(iter->first) ? iter = settings_nodes.erase(iter) : ++iter
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::isTenantActive(const string &name) const
|
||||
{
|
||||
return name == default_tenant_id || tenant_mananger->isTenantActive(name);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::periodicRegistrationRefresh()
|
||||
{
|
||||
I_Environment *environment = Singleton::Consume<I_Environment>::by<ConfigComponent>();
|
||||
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::by<ConfigComponent>();
|
||||
|
||||
while (true) {
|
||||
auto env_listening_port = environment->get<int>("Listening Port");
|
||||
|
||||
if (!env_listening_port.ok()) {
|
||||
dbgTrace(D_CONFIG)
|
||||
<< "Internal rest server listening port is not yet set."
|
||||
<< " Setting retry attempt to 500 milliseconds from now";
|
||||
mainloop->yield(chrono::milliseconds(500));
|
||||
} else if (!sendOrchestatorConfMsg(env_listening_port.unpack())) {
|
||||
mainloop->yield(chrono::milliseconds(500));
|
||||
} else {
|
||||
uint next_iteration_in_sec = getConfigurationWithDefault<uint>(
|
||||
600,
|
||||
"Config Component",
|
||||
"Refresh config update registration time interval"
|
||||
);
|
||||
mainloop->yield(chrono::seconds(next_iteration_in_sec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::loadConfiguration(vector<shared_ptr<JSONInputArchive>> &file_archives, bool is_async)
|
||||
{
|
||||
auto mainloop = is_async ? Singleton::Consume<I_MainLoop>::by<ConfigComponent>() : nullptr;
|
||||
|
||||
for (auto &cb : configuration_prepare_cbs) {
|
||||
cb();
|
||||
}
|
||||
|
||||
try {
|
||||
for (auto &archive : file_archives) {
|
||||
for (auto &resource : expected_resources) {
|
||||
auto loaded = resource->loadConfiguration(*archive);
|
||||
if (loaded.ok()) new_resource_nodes[resource->getPath()] = loaded;
|
||||
if (is_async) mainloop->yield();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &archive : file_archives) {
|
||||
string curr_tennat = default_tenant_id;
|
||||
try {
|
||||
(*archive)(cereal::make_nvp("tenantID", curr_tennat));
|
||||
} catch (cereal::Exception &e) {
|
||||
}
|
||||
|
||||
for (auto &config : expected_configs) {
|
||||
auto loaded = config->loadConfiguration(*archive);
|
||||
if (!loaded.empty()) new_configuration_nodes[curr_tennat][config->getPath()] = move(loaded);
|
||||
if (is_async) mainloop->yield();
|
||||
}
|
||||
for (auto &setting : expected_settings) {
|
||||
auto loaded = setting->loadConfiguration(*archive);
|
||||
if (loaded.ok()) new_settings_nodes[curr_tennat][setting->getPath()] = move(loaded);
|
||||
if (is_async) mainloop->yield();
|
||||
}
|
||||
}
|
||||
} catch (const cereal::Exception &e) {
|
||||
return commitFailure(e.what());
|
||||
} catch (const Config::ConfigException &e) {
|
||||
return commitFailure(e.getError());
|
||||
} catch (const EnvironmentHelper::EvaluatorParseError &e) {
|
||||
return commitFailure(e.getError());
|
||||
}
|
||||
|
||||
return commitSuccess();
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::commitSuccess()
|
||||
{
|
||||
new_resource_nodes.clear();
|
||||
configuration_nodes = move(new_configuration_nodes);
|
||||
settings_nodes = move(new_settings_nodes);
|
||||
|
||||
AgentProfileSettings profile_agent_settings = getSettingWithDefault<AgentProfileSettings>(
|
||||
AgentProfileSettings::default_profile_settings,
|
||||
"agentSettings"
|
||||
);
|
||||
|
||||
AgentProfileSettings general_agent_settings = getSettingWithDefault<AgentProfileSettings>(
|
||||
AgentProfileSettings::default_profile_settings,
|
||||
"generalAgentSettings"
|
||||
);
|
||||
|
||||
auto tmp_general_settings = general_agent_settings.getSettings();
|
||||
|
||||
for (const pair<string, string> &profile_setting : profile_agent_settings.getSettings()) {
|
||||
tmp_general_settings.insert(profile_setting);
|
||||
}
|
||||
|
||||
profile_settings = tmp_general_settings;
|
||||
reloadFileSystemPaths();
|
||||
|
||||
for (auto &cb : configuration_commit_cbs) {
|
||||
cb();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::commitFailure(const string &error)
|
||||
{
|
||||
dbgError(D_CONFIG) << error;
|
||||
new_resource_nodes.clear();
|
||||
new_configuration_nodes.clear();
|
||||
new_settings_nodes.clear();
|
||||
for (auto &cb : configuration_abort_cbs) {
|
||||
cb();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ConfigComponent::Impl::reloadConfigurationImpl(const string &version, bool is_async)
|
||||
{
|
||||
auto env = Singleton::Consume<I_Environment>::by<ConfigComponent>();
|
||||
env->registerValue<string>("New Policy Version", version);
|
||||
auto cleanup = make_scope_exit([env] () { env->unregisterKey<string>("New Policy Version"); } );
|
||||
|
||||
map<string, shared_ptr<ifstream>> files;
|
||||
for (const auto &path : config_file_paths) {
|
||||
auto fullpath = config_directory_path + path;
|
||||
files.emplace(fullpath, make_shared<ifstream>(fullpath));
|
||||
}
|
||||
|
||||
const vector<string> &active_tenants = tenant_mananger ? tenant_mananger->fetchActiveTenants() : vector<string>();
|
||||
for (const auto &config_file : expected_configuration_files) {
|
||||
for (const auto &type : config_file.second) {
|
||||
if (type == ConfigFileType::RawData) continue;
|
||||
auto global_path = getPolicyConfigPath(config_file.first, type);
|
||||
if (files.find(global_path) == files.end()) {
|
||||
files.emplace(global_path, make_shared<ifstream>(global_path));
|
||||
}
|
||||
|
||||
for (auto &tenant : active_tenants) {
|
||||
auto tenant_path = getPolicyConfigPath(config_file.first, type, tenant);
|
||||
files.emplace(tenant_path, make_shared<ifstream>(tenant_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<JSONInputArchive>> archives;
|
||||
for (const auto &file : files) {
|
||||
if (file.second->is_open()) {
|
||||
archives.push_back(make_shared<JSONInputArchive>(*file.second));
|
||||
} else {
|
||||
dbgDebug(D_CONFIG) << "Could not open configuration file. Path: " << file.first;
|
||||
}
|
||||
}
|
||||
|
||||
bool res = loadConfiguration(archives, is_async);
|
||||
if (res) env->registerValue<string>("Current Policy Version", version);
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::Impl::reloadConfigurationContinuesWrapper(const string &version, uint id)
|
||||
{
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::by<ConfigComponent>();
|
||||
|
||||
LoadNewConfigurationStatus in_progress(id, false, false);
|
||||
auto routine_id = mainloop->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
std::chrono::seconds(30),
|
||||
[=] () { sendOrchestatorReloadStatusMsg(in_progress); },
|
||||
"A-Synchronize reload configuraion monitoring"
|
||||
);
|
||||
|
||||
bool res = reloadConfigurationImpl(version, true);
|
||||
|
||||
mainloop->stop(routine_id);
|
||||
LoadNewConfigurationStatus finished(id, !res, true);
|
||||
if (!res) finished.setError("Failed to reload configuration");
|
||||
sendOrchestatorReloadStatusMsg(finished);
|
||||
|
||||
is_continuous_report = false;
|
||||
}
|
||||
|
||||
ConfigComponent::ConfigComponent() : Component("ConfigComponent"), pimpl(make_unique<Impl>()) {}
|
||||
ConfigComponent::~ConfigComponent() {}
|
||||
|
||||
void
|
||||
ConfigComponent::preload()
|
||||
{
|
||||
registerExpectedConfiguration<string>("Config Component", "configuration path");
|
||||
registerExpectedConfiguration<uint>("Config Component", "Refresh config update registration time interval");
|
||||
registerExpectedResource<bool>("Config Component", "Config Load Test");
|
||||
registerExpectedSetting<AgentProfileSettings>("agentSettings");
|
||||
pimpl->preload();
|
||||
}
|
||||
|
||||
void
|
||||
ConfigComponent::init()
|
||||
{
|
||||
if (Singleton::exists<I_RestApi>()) {
|
||||
auto rest = Singleton::Consume<I_RestApi>::by<ConfigComponent>();
|
||||
rest->addRestCall<LoadNewConfiguration>(RestAction::SET, "new-configuration");
|
||||
}
|
||||
pimpl->init();
|
||||
}
|
||||
89
core/config/config_globals.cc
Normal file
89
core/config/config_globals.cc
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace Config;
|
||||
|
||||
void
|
||||
reportConfigurationError(const string &err)
|
||||
{
|
||||
throw ConfigException(err);
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const Errors &err)
|
||||
{
|
||||
switch (err) {
|
||||
case Config::Errors::MISSING_TAG: return os << "MISSING_TAG";
|
||||
case Config::Errors::MISSING_CONTEXT: return os << "MISSING_CONTEXT";
|
||||
case Config::Errors::BAD_NODE: return os << "BAD_NODE";
|
||||
}
|
||||
return os << "Unknown error";
|
||||
}
|
||||
|
||||
void
|
||||
registerConfigPrepareCb(ConfigCb cb)
|
||||
{
|
||||
Singleton::Consume<I_Config>::from<MockConfigProvider>()->registerConfigPrepareCb(cb);
|
||||
}
|
||||
|
||||
void
|
||||
registerConfigLoadCb(ConfigCb cb)
|
||||
{
|
||||
Singleton::Consume<I_Config>::from<MockConfigProvider>()->registerConfigLoadCb(cb);
|
||||
}
|
||||
|
||||
void
|
||||
registerConfigAbortCb(ConfigCb cb)
|
||||
{
|
||||
Singleton::Consume<I_Config>::from<MockConfigProvider>()->registerConfigAbortCb(cb);
|
||||
}
|
||||
|
||||
bool
|
||||
reloadConfiguration(const std::string &version)
|
||||
{
|
||||
auto res = Singleton::Consume<I_Config>::from<MockConfigProvider>()->reloadConfiguration(version, false, 0);
|
||||
return res == I_Config::AsyncLoadConfigStatus::Success;
|
||||
}
|
||||
|
||||
string
|
||||
getConfigurationFlag(const string &flag)
|
||||
{
|
||||
return Singleton::Consume<I_Config>::from<MockConfigProvider>()->getConfigurationFlag(flag);
|
||||
}
|
||||
|
||||
const string &
|
||||
getFilesystemPathConfig()
|
||||
{
|
||||
return Singleton::Consume<I_Config>::from<MockConfigProvider>()->getFilesystemPathConfig();
|
||||
}
|
||||
|
||||
const string &
|
||||
getLogFilesPathConfig()
|
||||
{
|
||||
return Singleton::Consume<I_Config>::from<MockConfigProvider>()->getLogFilesPathConfig();
|
||||
}
|
||||
|
||||
string
|
||||
getPolicyConfigPath(const string &name, ConfigFileType type, const string &tenant)
|
||||
{
|
||||
return Singleton::Consume<I_Config>::from<MockConfigProvider>()->getPolicyConfigPath(name, type, tenant);
|
||||
}
|
||||
|
||||
void
|
||||
registerExpectedConfigFile(const string &config_name, ConfigFileType type)
|
||||
{
|
||||
Singleton::Consume<I_Config>::from<MockConfigProvider>()->registerExpectedConfigFile(config_name, type);
|
||||
}
|
||||
79
core/config/config_specific.cc
Normal file
79
core/config/config_specific.cc
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using namespace cereal;
|
||||
using namespace std;
|
||||
|
||||
template<>
|
||||
void
|
||||
Config::ConfigLoader<bool>::readValue(JSONInputArchive &ar)
|
||||
{
|
||||
ar(make_nvp("value", value));
|
||||
}
|
||||
|
||||
template<>
|
||||
void
|
||||
Config::ConfigLoader<int>::readValue(JSONInputArchive &ar)
|
||||
{
|
||||
ar(make_nvp("value", value));
|
||||
}
|
||||
|
||||
template<>
|
||||
void
|
||||
Config::ConfigLoader<uint>::readValue(JSONInputArchive &ar)
|
||||
{
|
||||
ar(make_nvp("value", value));
|
||||
}
|
||||
|
||||
template<>
|
||||
void
|
||||
Config::ConfigLoader<string>::readValue(JSONInputArchive &ar)
|
||||
{
|
||||
ar(make_nvp("value", value));
|
||||
}
|
||||
|
||||
template<>
|
||||
bool
|
||||
Config::loadProfileSetting<bool>(const string &raw_value)
|
||||
{
|
||||
if (raw_value == "true") {
|
||||
return true;
|
||||
} else if (raw_value == "false") {
|
||||
return false;
|
||||
} else {
|
||||
throw Exception("Illegal Value");
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
int
|
||||
Config::loadProfileSetting<int>(const string &raw_value)
|
||||
{
|
||||
return stoi(raw_value);
|
||||
}
|
||||
|
||||
template<>
|
||||
uint
|
||||
Config::loadProfileSetting<uint>(const string &raw_value)
|
||||
{
|
||||
return stoul(raw_value);
|
||||
}
|
||||
|
||||
template<>
|
||||
string
|
||||
Config::loadProfileSetting<string>(const string &raw_value)
|
||||
{
|
||||
return raw_value;
|
||||
}
|
||||
63
core/config/include/profile_settings.h
Executable file
63
core/config/include/profile_settings.h
Executable file
@@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __PROFILE_SETTINGS_H__
|
||||
#define __PROFILE_SETTINGS_H__
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
class AgentProfileSettings
|
||||
{
|
||||
public:
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
std::vector<SingleSetting> settings;
|
||||
cereal::load(ar, settings);
|
||||
|
||||
for (const SingleSetting &setting : settings) {
|
||||
std::pair<std::string, std::string> single_setting = setting.getSetting();
|
||||
profile_settings[boost::algorithm::trim_copy(single_setting.first)] =
|
||||
boost::algorithm::trim_copy(single_setting.second);
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::string, std::string> & getSettings() const { return profile_settings; }
|
||||
static AgentProfileSettings default_profile_settings;
|
||||
|
||||
private:
|
||||
class SingleSetting
|
||||
{
|
||||
public:
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(
|
||||
cereal::make_nvp("key", key),
|
||||
cereal::make_nvp("value", value)
|
||||
);
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> getSetting() const { return std::make_pair(key, value); }
|
||||
|
||||
private:
|
||||
std::string key;
|
||||
std::string value;
|
||||
};
|
||||
std::map<std::string, std::string> profile_settings;
|
||||
};
|
||||
|
||||
#endif // __PROFILE_SETTINGS_H__
|
||||
1
core/connkey/CMakeLists.txt
Normal file
1
core/connkey/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_library(connkey connkey.cc connkey_eval.cc)
|
||||
241
core/connkey/connkey.cc
Executable file
241
core/connkey/connkey.cc
Executable file
@@ -0,0 +1,241 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "connkey.h"
|
||||
#include "debug.h"
|
||||
#include "config.h"
|
||||
#include "hash_combine.h"
|
||||
#include "enum_range.h"
|
||||
#include "cereal/types/memory.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CEREAL_CLASS_VERSION(IPAddr, 0);
|
||||
CEREAL_CLASS_VERSION(ConnKey, 0);
|
||||
|
||||
USE_DEBUG_FLAG(D_CONFIG);
|
||||
|
||||
static bool
|
||||
protoHasPorts(IPProto proto)
|
||||
{
|
||||
return (proto==IPPROTO_TCP) || (proto==IPPROTO_UDP);
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const IPType &t)
|
||||
{
|
||||
switch (t) {
|
||||
case IPType::V4: {
|
||||
return os << "IPv4";
|
||||
}
|
||||
case IPType::V6: {
|
||||
return os << "IPv6";
|
||||
}
|
||||
case IPType::UNINITIALIZED: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return os << "Invalid(" << static_cast<uint>(t) << ")";
|
||||
}
|
||||
|
||||
// Format an IP address. Use a pair, becuase it depends on the type (v4/v6)
|
||||
ostream &
|
||||
IPAddr::print(ostream &os) const
|
||||
{
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
const char *formatted_addr;
|
||||
|
||||
switch (type) {
|
||||
case IPType::V4: {
|
||||
formatted_addr = inet_ntop(AF_INET, &v4, buf, sizeof(buf));
|
||||
dbgAssert(formatted_addr == buf) << "Failed to convert an IPv4 address";
|
||||
break;
|
||||
}
|
||||
case IPType::V6: {
|
||||
formatted_addr = inet_ntop(AF_INET6, &v6, buf, sizeof(buf));
|
||||
dbgAssert(formatted_addr == buf) << "Failed to convert an IPv6 address";
|
||||
break;
|
||||
}
|
||||
case IPType::UNINITIALIZED: {
|
||||
formatted_addr = "Uninitialized IP address";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
formatted_addr = "?";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return os << formatted_addr;
|
||||
}
|
||||
|
||||
// Format a port numbers. Use a pair, becuase it depends on the protocl (only TCP/UDP have ports).
|
||||
static ostream &
|
||||
operator<<(ostream &os, pair<IPProto, PortNumber> pp)
|
||||
{
|
||||
if (protoHasPorts(get<0>(pp))) {
|
||||
os << "|" << get<1>(pp);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
ostream &
|
||||
ConnKey::print(ostream &os) const
|
||||
{
|
||||
if (src.type == IPType::UNINITIALIZED) return os << "<Uninitialized connection>";
|
||||
|
||||
return os << "<" <<
|
||||
src << make_pair(src.proto, src.port) <<
|
||||
" -> " <<
|
||||
dst << make_pair(dst.proto, dst.port) <<
|
||||
" " << static_cast<uint>(src.proto) << ">"; // Cast needed to print as a number.
|
||||
}
|
||||
|
||||
void
|
||||
ConnKey::reverse()
|
||||
{
|
||||
swap(src, dst);
|
||||
}
|
||||
|
||||
size_t
|
||||
ConnKey::hash() const
|
||||
{
|
||||
dbgAssert(src.type != IPType::UNINITIALIZED) << "ConnKey::hash was called on an uninitialized object";
|
||||
size_t seed = 0; // XXX: random seed for security?
|
||||
hashCombine(seed, static_cast<u_char>(src.type));
|
||||
hashCombine(seed, src.proto);
|
||||
hashCombine(seed, src);
|
||||
hashCombine(seed, dst);
|
||||
hashCombine(seed, src.port);
|
||||
hashCombine(seed, dst.port);
|
||||
return seed;
|
||||
}
|
||||
|
||||
size_t
|
||||
IPAddr::hash() const
|
||||
{
|
||||
size_t seed = 0;
|
||||
hashCombine(seed, v6.s6_addr32[0]);
|
||||
hashCombine(seed, v6.s6_addr32[1]);
|
||||
hashCombine(seed, v6.s6_addr32[2]);
|
||||
hashCombine(seed, v6.s6_addr32[3]);
|
||||
return seed;
|
||||
}
|
||||
|
||||
bool
|
||||
IPAddr::isInRange(const IPAddr &left, const IPAddr &right) const
|
||||
{
|
||||
return (*this >= left) && (*this <= right);
|
||||
}
|
||||
|
||||
Maybe<IPAddr>
|
||||
IPAddr::createIPAddr(const string &ip_text)
|
||||
{
|
||||
if (ip_text.find(':') == string::npos) {
|
||||
struct in_addr v4;
|
||||
if(inet_pton(AF_INET, ip_text.c_str(), &v4)!=0){
|
||||
return IPAddr(v4);
|
||||
}
|
||||
} else { // Found ':' - it's IPv6
|
||||
struct in6_addr v6;
|
||||
if(inet_pton(AF_INET6, ip_text.c_str(), &v6)!=0){
|
||||
return IPAddr(v6);
|
||||
}
|
||||
}
|
||||
return genError("String \'"+ ip_text +"\' is not a valid IPv4/IPv6 address");
|
||||
}
|
||||
|
||||
bool IPAddr::isValidIPAddr(const string &ip_text) { return createIPAddr(ip_text).ok(); }
|
||||
|
||||
IPAddressConfig::IPAddressConfig(const string &ip_string)
|
||||
{
|
||||
auto maybe_address = IPAddr::createIPAddr(ip_string);
|
||||
if (maybe_address.ok()) address = maybe_address.unpack();
|
||||
}
|
||||
|
||||
void
|
||||
IPAddressConfig::load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
string ip_string;
|
||||
ar(cereal::make_nvp("IPAddress", ip_string));
|
||||
auto ip_address = IPAddr::createIPAddr(ip_string);
|
||||
if (!ip_address.ok()) {
|
||||
throw Config::ConfigException(
|
||||
"Failed to create an IP address from " + ip_string + ": " + ip_address.getErr()
|
||||
);
|
||||
}
|
||||
address = ip_address.unpack();
|
||||
}
|
||||
|
||||
const string ConnKey::network_key = "NetworkKey";
|
||||
|
||||
template<typename Num>
|
||||
Maybe<Num>
|
||||
fromStringToNumeric(const string &value_str, const string &name, const int max_val)
|
||||
{
|
||||
if (value_str.find_first_not_of("0123456789") != string::npos) {
|
||||
dbgError(D_CONFIG) << name << " contains non digit chars. Value: " << value_str;
|
||||
return genError(name + " contains non digit chars. Value: " + value_str);
|
||||
}
|
||||
try {
|
||||
int value;
|
||||
value = stoi(value_str);
|
||||
if (value > max_val) {
|
||||
dbgError(D_CONFIG) << "Invalid " << name << ". Value: " << value_str;
|
||||
return genError("Invalid " + name + ". Value: " + value_str);
|
||||
}
|
||||
return static_cast<Num>(value);
|
||||
} catch (const invalid_argument &e) {
|
||||
dbgError(D_CONFIG) << name << " resived is invalid. Error: " << e.what();
|
||||
return genError(name + " recived is invalid. Error: " + e.what());
|
||||
}
|
||||
return genError("Error in creating numeric value of " + name);
|
||||
}
|
||||
|
||||
bool
|
||||
ConnKeyUtil::fromString(const string &proto_str, IPProto &proto)
|
||||
{
|
||||
Maybe<IPProto> ip_protocol = fromStringToNumeric<IPProto>(proto_str, "Ip protocol", 255);
|
||||
if (ip_protocol.ok()) {
|
||||
proto = ip_protocol.unpack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ConnKeyUtil::fromString(const string &port_str, PortNumber &port)
|
||||
{
|
||||
Maybe<PortNumber> port_num = fromStringToNumeric<PortNumber>(port_str, "Port", 65535);
|
||||
if (port_num.ok()) {
|
||||
port = port_num.unpack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ConnKeyUtil::fromString(const string &ip_str, IPAddr &ip_address)
|
||||
{
|
||||
Maybe<IPAddr> ip_addr = IPAddr::createIPAddr(ip_str);
|
||||
if (!ip_addr.ok()) {
|
||||
dbgError(D_CONFIG) << "Ip address resived is invalid: " << ip_addr.getErr();
|
||||
return false;
|
||||
}
|
||||
ip_address = ip_addr.unpack();
|
||||
return true;
|
||||
}
|
||||
167
core/connkey/connkey_eval.cc
Normal file
167
core/connkey/connkey_eval.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "connkey.h"
|
||||
#include "environment/evaluator_templates.h"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace EnvironmentHelper;
|
||||
|
||||
class ConstantPort : public Constant<uint16_t>
|
||||
{
|
||||
public:
|
||||
ConstantPort(const vector<string> ¶ms)
|
||||
:
|
||||
Constant<uint16_t>(
|
||||
[] (const string & str) {
|
||||
uint16_t value = 0;
|
||||
try {
|
||||
value = boost::lexical_cast<uint16_t>(str);
|
||||
} catch (boost::bad_lexical_cast const&) {
|
||||
reportWrongParamType(getName(), str, "Not a port number");
|
||||
}
|
||||
return value;
|
||||
},
|
||||
params
|
||||
) {}
|
||||
|
||||
static string getName() { return Constant<uint16_t>::getName() + "Port"; }
|
||||
};
|
||||
|
||||
class ConstantIP : public Constant<IPAddr>
|
||||
{
|
||||
public:
|
||||
ConstantIP(const vector<string> ¶ms)
|
||||
:
|
||||
Constant<IPAddr>(
|
||||
[] (const string & str) {
|
||||
auto addr = IPAddr::createIPAddr(str);
|
||||
if (!addr.ok()) reportWrongParamType(getName(), str, "Not an IP address");
|
||||
return *addr;
|
||||
},
|
||||
params
|
||||
) {}
|
||||
|
||||
static string getName() { return Constant<IPAddr>::getName() + "IP"; }
|
||||
};
|
||||
|
||||
class ConstantProtocol : public Constant<IPProto>
|
||||
{
|
||||
public:
|
||||
ConstantProtocol(const vector<string> ¶ms)
|
||||
:
|
||||
Constant<IPProto>(
|
||||
[] (const string &str) {
|
||||
uint16_t value = 0;
|
||||
for (auto &ch : str) {
|
||||
if (ch < '0' || ch > '9') reportWrongParamType(getName(), str, "Not a protocol ID character");
|
||||
value = value * 10 + (ch - '0');
|
||||
if (256 <= value) reportWrongParamType(getName(), str, "Not a protocol ID number");
|
||||
}
|
||||
return static_cast<IPProto>(value);
|
||||
},
|
||||
params
|
||||
) {}
|
||||
|
||||
static string getName() { return Constant<IPProto>::getName() + "Protocol"; }
|
||||
};
|
||||
|
||||
class EqualPort : public Equal<uint16_t>
|
||||
{
|
||||
public:
|
||||
EqualPort(const vector<string> ¶ms) : Equal<uint16_t>(params) {}
|
||||
static string getName() { return Equal<uint16_t>::getName() + "Port"; }
|
||||
};
|
||||
|
||||
class EqualIP : public Equal<IPAddr>
|
||||
{
|
||||
public:
|
||||
EqualIP(const vector<string> ¶ms) : Equal<IPAddr>(params) {}
|
||||
static string getName() { return Equal<IPAddr>::getName() + "IP"; }
|
||||
};
|
||||
|
||||
class EqualProtocol : public Equal<IPProto>
|
||||
{
|
||||
public:
|
||||
EqualProtocol(const vector<string> ¶ms) : Equal<IPProto>(params) {}
|
||||
static string getName() { return Equal<IPProto>::getName() + "Protocol"; }
|
||||
};
|
||||
|
||||
class DPort : public Invoker<uint16_t, ConnKey>
|
||||
{
|
||||
public:
|
||||
DPort(const vector<string> ¶ms)
|
||||
:
|
||||
Invoker<uint16_t, ConnKey>([] (const ConnKey &key) { return key.getDPort(); }, params) {}
|
||||
static string getName() { return Invoker<uint16_t, ConnKey>::getName() + "DPort"; }
|
||||
};
|
||||
|
||||
class SPort : public Invoker<uint16_t, ConnKey>
|
||||
{
|
||||
public:
|
||||
SPort(const vector<string> ¶ms)
|
||||
:
|
||||
Invoker<uint16_t, ConnKey>([] (const ConnKey &key) { return key.getSPort(); }, params) {}
|
||||
|
||||
static string getName() { return Invoker<uint16_t, ConnKey>::getName() + "SPort"; }
|
||||
};
|
||||
|
||||
class Dst : public Invoker<IPAddr, ConnKey>
|
||||
{
|
||||
public:
|
||||
Dst(const vector<string> ¶ms)
|
||||
:
|
||||
Invoker<IPAddr, ConnKey>([] (const ConnKey &key) { return key.getDst(); }, params) {}
|
||||
|
||||
static string getName() { return Invoker<IPAddr, ConnKey>::getName() + "Dst"; }
|
||||
};
|
||||
|
||||
class Src : public Invoker<IPAddr, ConnKey>
|
||||
{
|
||||
public:
|
||||
Src(const vector<string> ¶ms)
|
||||
:
|
||||
Invoker<IPAddr, ConnKey>([] (const ConnKey &key) { return key.getSrc(); }, params) {}
|
||||
|
||||
static string getName() { return Invoker<IPAddr, ConnKey>::getName() + "Src"; }
|
||||
};
|
||||
|
||||
class Protocol : public Invoker<IPProto, ConnKey>
|
||||
{
|
||||
public:
|
||||
Protocol(const vector<string> ¶ms)
|
||||
:
|
||||
Invoker<IPProto, ConnKey>([] (const ConnKey &key) { return key.getProto(); }, params) {}
|
||||
|
||||
static string getName() { return Invoker<IPProto, ConnKey>::getName() + "Protocol"; }
|
||||
};
|
||||
|
||||
void
|
||||
ConnKey::preload()
|
||||
{
|
||||
addMatcher<ConstantPort>();
|
||||
addMatcher<ConstantIP>();
|
||||
addMatcher<ConstantProtocol>();
|
||||
|
||||
addMatcher<EqualPort>();
|
||||
addMatcher<EqualIP>();
|
||||
addMatcher<EqualProtocol>();
|
||||
|
||||
addMatcher<DPort>();
|
||||
addMatcher<SPort>();
|
||||
addMatcher<Dst>();
|
||||
addMatcher<Src>();
|
||||
addMatcher<Protocol>();
|
||||
}
|
||||
5
core/core_ut/CMakeLists.txt
Normal file
5
core/core_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
core_ut
|
||||
"tostring_ut.cc;maybe_res_ut.cc;enum_range_ut.cc;enum_array_ut.cc;cache_ut.cc;common_ut.cc;virtual_container_ut.cc;"
|
||||
"singleton;rest"
|
||||
)
|
||||
172
core/core_ut/cache_ut.cc
Normal file
172
core/core_ut/cache_ut.cc
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "cache.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace chrono;
|
||||
using namespace testing;
|
||||
|
||||
class Int
|
||||
{
|
||||
public:
|
||||
Int() {}
|
||||
Int(const int &_val) : val(_val) {}
|
||||
Int(int &&_val) : val(move(_val)) {}
|
||||
|
||||
operator int() { return val; }
|
||||
Int & operator=(const int _val) { val = _val; return *this; }
|
||||
bool operator==(const int _val) const { return val == _val; }
|
||||
|
||||
private:
|
||||
int val = 0;
|
||||
};
|
||||
|
||||
TEST(TempCaching, value_existing)
|
||||
{
|
||||
TemporaryCache<int, Int> cache;
|
||||
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
|
||||
cache.createEntry(0);
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
|
||||
cache.deleteEntry(0);
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
}
|
||||
|
||||
TEST(TempCaching, void_existing)
|
||||
{
|
||||
TemporaryCache<int, void> cache;
|
||||
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
|
||||
cache.createEntry(0);
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
|
||||
cache.deleteEntry(0);
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
}
|
||||
|
||||
TEST(TempCaching, value_get)
|
||||
{
|
||||
TemporaryCache<int, Int> cache;
|
||||
cache.createEntry(0);
|
||||
|
||||
EXPECT_EQ(cache.getEntry(0), 0);
|
||||
|
||||
cache.getEntry(0) = 9;
|
||||
|
||||
EXPECT_EQ(cache.getEntry(0), 9);
|
||||
}
|
||||
|
||||
TEST(TempCaching, value_emplace)
|
||||
{
|
||||
TemporaryCache<int, Int> cache;
|
||||
int val = 9;
|
||||
|
||||
cache.emplaceEntry(0, val);
|
||||
EXPECT_EQ(cache.getEntry(0), 9);
|
||||
EXPECT_EQ(val, 9);
|
||||
|
||||
|
||||
cache.emplaceEntry(1, move(val));
|
||||
EXPECT_EQ(cache.getEntry(0), 9);
|
||||
EXPECT_EQ(cache.getEntry(1), 9);
|
||||
EXPECT_EQ(val, 9);
|
||||
}
|
||||
|
||||
TEST(TempCaching, get_uninitialized_value)
|
||||
{
|
||||
TemporaryCache<int, Int> cache;
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
|
||||
EXPECT_EQ(cache.getEntry(0), 0);
|
||||
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
}
|
||||
|
||||
TEST(TempCaching, expiration)
|
||||
{
|
||||
StrictMock<MockMainLoop> mock_ml;
|
||||
auto i_mainloop = Singleton::Consume<I_MainLoop>::from<MockProvider<I_MainLoop>>();
|
||||
StrictMock<MockTimeGet> mock_time;
|
||||
auto i_time_get = Singleton::Consume<I_TimeGet>::from<MockProvider<I_TimeGet>>();
|
||||
TemporaryCache<int, Int> cache;
|
||||
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
cache.createEntry(0);
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
|
||||
EXPECT_CALL(mock_ml, doesRoutineExist(0)).WillOnce(Return(false));
|
||||
I_MainLoop::Routine routine;
|
||||
EXPECT_CALL(mock_ml, addRecurringRoutine(_, _, _, _, _)).WillOnce(DoAll(SaveArg<2>(&routine), Return(1)));
|
||||
cache.startExpiration(seconds(10), i_mainloop, i_time_get);
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillOnce(Return(seconds(2)));
|
||||
cache.createEntry(0);
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillOnce(Return(seconds(6)));
|
||||
cache.createEntry(1);
|
||||
EXPECT_TRUE(cache.doesKeyExists(0));
|
||||
EXPECT_TRUE(cache.doesKeyExists(1));
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillOnce(Return(seconds(14)));
|
||||
routine();
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
EXPECT_TRUE(cache.doesKeyExists(1));
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillOnce(Return(seconds(24)));
|
||||
routine();
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
EXPECT_FALSE(cache.doesKeyExists(1));
|
||||
|
||||
EXPECT_CALL(mock_ml, doesRoutineExist(1)).WillOnce(Return(true));
|
||||
EXPECT_CALL(mock_ml, stop(1));
|
||||
cache.endExpiration();
|
||||
}
|
||||
|
||||
TEST(TempCaching, capacity)
|
||||
{
|
||||
TemporaryCache<int, Int> cache;
|
||||
cache.createEntry(0);
|
||||
cache.createEntry(1);
|
||||
cache.createEntry(2);
|
||||
cache.createEntry(3);
|
||||
cache.createEntry(4);
|
||||
|
||||
EXPECT_EQ(cache.size(), 5);
|
||||
EXPECT_EQ(cache.capacity(), 0);
|
||||
cache.capacity(3);
|
||||
EXPECT_EQ(cache.size(), 3);
|
||||
EXPECT_FALSE(cache.doesKeyExists(0));
|
||||
EXPECT_FALSE(cache.doesKeyExists(1));
|
||||
EXPECT_TRUE(cache.doesKeyExists(2));
|
||||
EXPECT_TRUE(cache.doesKeyExists(3));
|
||||
EXPECT_TRUE(cache.doesKeyExists(4));
|
||||
|
||||
cache.createEntry(5);
|
||||
EXPECT_EQ(cache.size(), 3);
|
||||
EXPECT_FALSE(cache.doesKeyExists(2));
|
||||
EXPECT_TRUE(cache.doesKeyExists(3));
|
||||
EXPECT_TRUE(cache.doesKeyExists(4));
|
||||
EXPECT_TRUE(cache.doesKeyExists(5));
|
||||
|
||||
cache.capacity(0);
|
||||
cache.createEntry(6);
|
||||
EXPECT_EQ(cache.size(), 4);
|
||||
EXPECT_TRUE(cache.doesKeyExists(3));
|
||||
EXPECT_TRUE(cache.doesKeyExists(4));
|
||||
EXPECT_TRUE(cache.doesKeyExists(5));
|
||||
EXPECT_TRUE(cache.doesKeyExists(6));
|
||||
|
||||
cache.deleteEntry(5);
|
||||
cache.capacity(2);
|
||||
EXPECT_EQ(cache.size(), 2);
|
||||
EXPECT_TRUE(cache.doesKeyExists(4));
|
||||
EXPECT_TRUE(cache.doesKeyExists(6));
|
||||
}
|
||||
233
core/core_ut/common_ut.cc
Normal file
233
core/core_ut/common_ut.cc
Normal file
@@ -0,0 +1,233 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "customized_cereal_map.h"
|
||||
#include "customized_cereal_multimap.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace cereal;
|
||||
|
||||
TEST(MakeSeperatedStr, array)
|
||||
{
|
||||
array<int, 5> arr = { 1, 2, 3, 4, 5 };
|
||||
EXPECT_EQ(makeSeparatedStr(arr, " - "), "1 - 2 - 3 - 4 - 5");
|
||||
}
|
||||
|
||||
TEST(MakeSeperatedStr, vector)
|
||||
{
|
||||
vector<string> vec = { "aaa", "b", "c c", "dd" };
|
||||
EXPECT_EQ(makeSeparatedStr(vec, ", "), "aaa, b, c c, dd");
|
||||
}
|
||||
|
||||
TEST(Debug, dump_printable_char)
|
||||
{
|
||||
char ch = 'c';
|
||||
EXPECT_EQ(dumpHexChar(ch), string("'c'"));
|
||||
}
|
||||
|
||||
TEST(Debug, dump_non_printable_char)
|
||||
{
|
||||
char ch = 0x1B;
|
||||
EXPECT_EQ(dumpHexChar(ch), string("\\x1b"));
|
||||
}
|
||||
|
||||
TEST(Debug, dump_hex)
|
||||
{
|
||||
EXPECT_EQ(dumpHex(string("hello")), "hello");
|
||||
EXPECT_EQ(dumpHex(string("a\\b")), "a\\\\b");
|
||||
EXPECT_EQ(dumpHex(string("a\tb")), "a\\x09b");
|
||||
EXPECT_EQ(dumpHex(vector<char>({ 'w', 'o', 'r', 'l', 'd'})), "world");
|
||||
int tst_numeric[4] = {1, 10, 200, 201};
|
||||
EXPECT_EQ(dumpHex(tst_numeric), "\\x01\\x0a\\xc8\\xc9");
|
||||
}
|
||||
|
||||
TEST(Debug, dump_real_hex)
|
||||
{
|
||||
EXPECT_EQ(dumpRealHex(string("hello")), " 68 65 6c 6c 6f");
|
||||
EXPECT_EQ(dumpRealHex(string("a\\b")), " 61 5c 62");
|
||||
EXPECT_EQ(dumpRealHex(string("a\tb")), " 61 09 62");
|
||||
EXPECT_EQ(dumpRealHex(vector<char>({ 'w', 'o', 'r', 'l', 'd'})), " 77 6f 72 6c 64");
|
||||
int tst_numeric[4] = {1, 10, 200, 201};
|
||||
EXPECT_EQ(dumpRealHex(tst_numeric), " 01 0a c8 c9");
|
||||
}
|
||||
|
||||
class Aaaa {};
|
||||
|
||||
class B {};
|
||||
|
||||
ostream & operator<<(ostream &os, const B &) { return os; }
|
||||
|
||||
TEST(Printable, check_if_printable)
|
||||
{
|
||||
EXPECT_FALSE(IsPrintable<Aaaa>());
|
||||
EXPECT_TRUE(IsPrintable<B>());
|
||||
EXPECT_TRUE(IsPrintable<int>());
|
||||
EXPECT_TRUE(IsPrintable<string>());
|
||||
}
|
||||
|
||||
class TestCerealMap : public testing::Test
|
||||
{
|
||||
public:
|
||||
template<typename Type>
|
||||
string
|
||||
serializeMap(const map<string, Type> &test_map, const string &map_key)
|
||||
{
|
||||
std::stringstream out;
|
||||
{
|
||||
cereal::JSONOutputArchive out_ar(out);
|
||||
out_ar(cereal::make_nvp(map_key, test_map));
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
map<string, Type>
|
||||
deserializeMap(const string &map_text, const string &map_key)
|
||||
{
|
||||
map<string, Type> ret_value;
|
||||
std::stringstream in;
|
||||
in << map_text;
|
||||
{
|
||||
cereal::JSONInputArchive in_ar(in);
|
||||
in_ar(cereal::make_nvp(map_key, ret_value));
|
||||
}
|
||||
return ret_value;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TestCerealMap, serialize)
|
||||
{
|
||||
map<string, string> strings_map = {{"fi", "fa"}, {"fo", "fam"}, {"bisli", "bamba"}};
|
||||
map<string, int> ints_map = {{"4", 2}, {"42", 420}};
|
||||
using strings = vector<string>;
|
||||
map<string, strings> strings_vectors_map = {{"1", strings({"2", "3"})}};
|
||||
EXPECT_EQ(
|
||||
serializeMap<string>(strings_map, "strings_map"),
|
||||
"{\n"
|
||||
" \"strings_map\": {\n"
|
||||
" \"bisli\": \"bamba\",\n"
|
||||
" \"fi\": \"fa\",\n"
|
||||
" \"fo\": \"fam\"\n"
|
||||
" }\n"
|
||||
"}"
|
||||
|
||||
);
|
||||
EXPECT_EQ(
|
||||
serializeMap<int>(ints_map, "ints_map"),
|
||||
"{\n"
|
||||
" \"ints_map\": {\n"
|
||||
" \"4\": 2,\n"
|
||||
" \"42\": 420\n"
|
||||
" }\n"
|
||||
"}"
|
||||
|
||||
);
|
||||
EXPECT_EQ(
|
||||
serializeMap<strings>(strings_vectors_map, "strings_vectors_map"),
|
||||
"{\n"
|
||||
" \"strings_vectors_map\": {\n"
|
||||
" \"1\": [\n"
|
||||
" \"2\",\n"
|
||||
" \"3\"\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(TestCerealMap, desirialize)
|
||||
{
|
||||
string map_str = "{\"bool_map\" :{\"true\": true, \"false\": false }}";
|
||||
map<string, bool> expected_bools_map({{"true", true}, {"false", false}});
|
||||
EXPECT_EQ(deserializeMap<bool>(map_str, "bool_map"), expected_bools_map);
|
||||
|
||||
map_str = "{\"string_map\" :{\"str\": \"str\", \"char *\": \"char *\" }}";
|
||||
map<string, string> expected_string_map({{"str", "str"}, {"char *", "char *"}});
|
||||
EXPECT_EQ(deserializeMap<string>(map_str, "string_map"), expected_string_map);
|
||||
|
||||
map_str = "{\"strings_vectors_map\" :{\"hello\": [\"world\", \"universe\"], \"hi\": [\"space\"] }}";
|
||||
using strings = vector<string>;
|
||||
|
||||
map<string, strings> expected_strings_vectors_map(
|
||||
{{"hello", strings({"world", "universe"})},
|
||||
{"hi", strings({"space"})}}
|
||||
);
|
||||
EXPECT_EQ(deserializeMap<strings>(map_str, "strings_vectors_map"), expected_strings_vectors_map);
|
||||
}
|
||||
|
||||
TEST(TestCerealMultimap, regularStringMap)
|
||||
{
|
||||
SerializableMultiMap<string> m;
|
||||
|
||||
string data_str =
|
||||
"{\n"
|
||||
" \"multimap\": {\n"
|
||||
" \"user\": \"omry\"\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
stringstream is;
|
||||
is << data_str;
|
||||
JSONInputArchive ar(is);
|
||||
ar(make_nvp("multimap", m));
|
||||
|
||||
EXPECT_EQ(m.getMap<string>()["user"], "omry");
|
||||
}
|
||||
|
||||
TEST(TestCerealMultimap, mixedPrimitivesMap)
|
||||
{
|
||||
SerializableMultiMap<string, int, bool> m;
|
||||
|
||||
string data_str =
|
||||
"{\n"
|
||||
" \"multimap\": {\n"
|
||||
" \"user\": \"omry\",\n"
|
||||
" \"number\": 14,\n"
|
||||
" \"king of cpp\": true\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
stringstream is;
|
||||
is << data_str;
|
||||
JSONInputArchive ar(is);
|
||||
ar(make_nvp("multimap", m));
|
||||
|
||||
EXPECT_EQ(m.getMap<string>()["user"], "omry");
|
||||
EXPECT_EQ(m.getMap<int>()["number"], 14);
|
||||
EXPECT_EQ(m.getMap<bool>()["king of cpp"], true);
|
||||
}
|
||||
|
||||
TEST(TestCerealMultimap, mixedPrimitivesAndObjectsMap)
|
||||
{
|
||||
SerializableMultiMap<string, int, bool, vector<string>> m;
|
||||
|
||||
string data_str =
|
||||
"{\n"
|
||||
" \"multimap\": {\n"
|
||||
" \"user\": \"omry\",\n"
|
||||
" \"number\": 14,\n"
|
||||
" \"king of cpp\": true,\n"
|
||||
" \"friends\": [\n"
|
||||
" \"Max\",\n"
|
||||
" \"David\",\n"
|
||||
" \"Daniel\",\n"
|
||||
" \"Oren\",\n"
|
||||
" \"Roi\",\n"
|
||||
" \"Moaad\"\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
stringstream is;
|
||||
is << data_str;
|
||||
JSONInputArchive ar(is);
|
||||
ar(make_nvp("multimap", m));
|
||||
|
||||
EXPECT_EQ(m.getMap<string>()["user"], "omry");
|
||||
EXPECT_EQ(m.getMap<int>()["number"], 14);
|
||||
EXPECT_EQ(m.getMap<bool>()["king of cpp"], true);
|
||||
EXPECT_EQ(m.getMap<vector<string>>()["friends"].front(), "Max");
|
||||
}
|
||||
85
core/core_ut/enum_array_ut.cc
Normal file
85
core/core_ut/enum_array_ut.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "enum_array.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
enum class Test1 { val1, val2, val3, val4, COUNT };
|
||||
|
||||
TEST(enum_array, enum_with_count)
|
||||
{
|
||||
EnumArray<Test1, int> arr(0, 1, 2, 4);
|
||||
EXPECT_EQ(arr[Test1::val1], 0);
|
||||
EXPECT_EQ(arr[Test1::val2], 1);
|
||||
EXPECT_EQ(arr[Test1::val3], 2);
|
||||
EXPECT_EQ(arr[Test1::val4], 4);
|
||||
|
||||
arr[Test1::val4] = 3;
|
||||
EXPECT_EQ(arr[Test1::val4], 3);
|
||||
|
||||
vector<int> vals;
|
||||
for (auto num : arr) {
|
||||
vals.push_back(num);
|
||||
}
|
||||
vector<int> expected = { 0, 1, 2, 3 };
|
||||
EXPECT_EQ(vals, expected);
|
||||
}
|
||||
|
||||
TEST(enum_array, auto_fill)
|
||||
{
|
||||
EnumArray<Test1, int> arr(EnumArray<Test1, int>::Fill(), 18);
|
||||
|
||||
vector<int> vals;
|
||||
for (auto num : arr) {
|
||||
vals.push_back(num);
|
||||
}
|
||||
vector<int> expected = { 18, 18, 18, 18 };
|
||||
EXPECT_EQ(vals, expected);
|
||||
}
|
||||
|
||||
enum class Test2 { val1, val2, val3, val4 };
|
||||
template <>
|
||||
class EnumCount<Test2> : public EnumCountSpecialization<Test2, 4>
|
||||
{
|
||||
};
|
||||
|
||||
TEST(enum_array, enum_with_template_specialization)
|
||||
{
|
||||
EnumArray<Test2, int> arr(0, 1, 2, 4);
|
||||
EXPECT_EQ(arr[Test2::val1], 0);
|
||||
EXPECT_EQ(arr[Test2::val2], 1);
|
||||
EXPECT_EQ(arr[Test2::val3], 2);
|
||||
EXPECT_EQ(arr[Test2::val4], 4);
|
||||
|
||||
arr[Test2::val4] = 3;
|
||||
EXPECT_EQ(arr[Test2::val4], 3);
|
||||
|
||||
vector<int> vals;
|
||||
for (auto num : arr) {
|
||||
vals.push_back(num);
|
||||
}
|
||||
vector<int> expected = { 0, 1, 2, 3 };
|
||||
EXPECT_EQ(vals, expected);
|
||||
}
|
||||
|
||||
enum class Test3 { val1, val2, val3, val4 };
|
||||
|
||||
TEST(enum_array, array_with_explicit_length)
|
||||
{
|
||||
EnumArray<Test3, int, 4> arr(0, 1, 2, 4);
|
||||
EXPECT_EQ(arr[Test3::val1], 0);
|
||||
EXPECT_EQ(arr[Test3::val2], 1);
|
||||
EXPECT_EQ(arr[Test3::val3], 2);
|
||||
EXPECT_EQ(arr[Test3::val4], 4);
|
||||
|
||||
arr[Test3::val4] = 3;
|
||||
EXPECT_EQ(arr[Test3::val4], 3);
|
||||
|
||||
vector<int> vals;
|
||||
for (auto num : arr) {
|
||||
vals.push_back(num);
|
||||
}
|
||||
vector<int> expected = { 0, 1, 2, 3 };
|
||||
EXPECT_EQ(vals, expected);
|
||||
}
|
||||
50
core/core_ut/enum_range_ut.cc
Normal file
50
core/core_ut/enum_range_ut.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "enum_range.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
using testing::ElementsAre;
|
||||
|
||||
enum class Test1 { val1, val2, val3, val4, COUNT };
|
||||
|
||||
TEST(enum_range, whole_range)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<Test1>(), ElementsAre(Test1::val1, Test1::val2, Test1::val3, Test1::val4));
|
||||
EXPECT_THAT(makeRange<Test1>(), ElementsAre(Test1::val1, Test1::val2, Test1::val3, Test1::val4));
|
||||
}
|
||||
|
||||
TEST(enum_range, up_to_point)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<Test1>(Test1::val3), ElementsAre(Test1::val1, Test1::val2, Test1::val3));
|
||||
EXPECT_THAT(makeRange(Test1::val3), ElementsAre(Test1::val1, Test1::val2, Test1::val3));
|
||||
}
|
||||
|
||||
TEST(enum_range, slice_range)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<Test1>(Test1::val2, Test1::val3), ElementsAre(Test1::val2, Test1::val3));
|
||||
EXPECT_THAT(makeRange(Test1::val2, Test1::val3), ElementsAre(Test1::val2, Test1::val3));
|
||||
}
|
||||
|
||||
|
||||
enum class Test2 { val1, val2, val3, val4 };
|
||||
template <>
|
||||
class EnumCount<Test2> : public EnumCountSpecialization<Test2, 4> {};
|
||||
|
||||
TEST(enum_range, whole_range_without_count_elem)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<Test2>(), ElementsAre(Test2::val1, Test2::val2, Test2::val3, Test2::val4));
|
||||
EXPECT_THAT(makeRange<Test2>(), ElementsAre(Test2::val1, Test2::val2, Test2::val3, Test2::val4));
|
||||
}
|
||||
|
||||
TEST(enum_range, int_up_point)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<int>(9), ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||
EXPECT_THAT(makeRange(9), ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||
}
|
||||
|
||||
TEST(enum_range, int_slice_range)
|
||||
{
|
||||
EXPECT_THAT(NGEN::Range<int>(5, 10), ElementsAre(5, 6, 7, 8, 9, 10));
|
||||
EXPECT_THAT(makeRange(5, 10), ElementsAre(5, 6, 7, 8, 9, 10));
|
||||
}
|
||||
406
core/core_ut/maybe_res_ut.cc
Executable file
406
core/core_ut/maybe_res_ut.cc
Executable file
@@ -0,0 +1,406 @@
|
||||
#include "cptest.h"
|
||||
#include "maybe_res.h"
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
static Maybe<int>
|
||||
returnIfEven(int i)
|
||||
{
|
||||
if (i%2 == 1) return genError("Odd number");
|
||||
return i;
|
||||
}
|
||||
|
||||
TEST(Usage, typical_function)
|
||||
{
|
||||
auto even = returnIfEven(4);
|
||||
ASSERT_TRUE(even.ok());
|
||||
EXPECT_EQ(4, *even);
|
||||
EXPECT_THAT(even, IsValue(4));
|
||||
|
||||
auto odd = returnIfEven(5);
|
||||
ASSERT_FALSE(odd.ok());
|
||||
EXPECT_EQ("Odd number", odd.getErr());
|
||||
EXPECT_THAT(odd, IsError("Odd number"));
|
||||
}
|
||||
|
||||
TEST(genError, explicit_build)
|
||||
{
|
||||
Error<string> err = genError<string>("error");
|
||||
}
|
||||
|
||||
TEST(genError, implicit_build)
|
||||
{
|
||||
Error<string> err = genError(string("error"));
|
||||
}
|
||||
|
||||
TEST(genError, paramaters_build)
|
||||
{
|
||||
string str = "error";
|
||||
Error<vector<char>> err = genError<vector<char>>(str.begin(), str.end());
|
||||
}
|
||||
|
||||
TEST(genError, void_build)
|
||||
{
|
||||
Error<void> err1 = genError<void>();
|
||||
Error<void> err2 = genError<void>(5, 6, 7);
|
||||
EXPECT_TRUE(err1 == err2);
|
||||
}
|
||||
|
||||
TEST(Maybe, basic_error)
|
||||
{
|
||||
Maybe<int> res = genError<string>("error");
|
||||
ASSERT_FALSE(res.ok());
|
||||
EXPECT_EQ(string("error"), res.getErr());
|
||||
EXPECT_THAT(res, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(Maybe, basic_value)
|
||||
{
|
||||
Maybe<int> res = 5;
|
||||
ASSERT_TRUE(res.ok());
|
||||
EXPECT_EQ(5, res.unpack());
|
||||
EXPECT_EQ(5, static_cast<int>(*res));
|
||||
EXPECT_EQ(5, *res);
|
||||
EXPECT_THAT(res, IsValue(5));
|
||||
}
|
||||
|
||||
TEST(Maybe, error_cast)
|
||||
{
|
||||
Maybe<int> res = genError<const char *>("error");
|
||||
EXPECT_THAT(res, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(Maybe, error_cast_impilicit)
|
||||
{
|
||||
Maybe<int> res = genError("error");
|
||||
EXPECT_THAT(res, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(Maybe, unpack_execption)
|
||||
{
|
||||
Maybe<int> res = 5;
|
||||
EXPECT_EQ(5, res.unpack<string>());
|
||||
|
||||
Maybe<int> err = genError("error");
|
||||
|
||||
EXPECT_THROW(err.unpack<string>(), string);
|
||||
try {
|
||||
err.unpack<string>();
|
||||
} catch (string str) {
|
||||
EXPECT_EQ("error", str);
|
||||
}
|
||||
|
||||
EXPECT_THROW(err.unpack<string>("really ", "bad "), string);
|
||||
try {
|
||||
err.unpack<string>("really ", "bad ");
|
||||
} catch (string str) {
|
||||
EXPECT_EQ("really bad error", str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Maybe, verify)
|
||||
{
|
||||
Maybe<int> res = 5;
|
||||
res.verify<string>();
|
||||
res.verify<string>("really ", "bad ");
|
||||
|
||||
Maybe<int> err = genError("error");
|
||||
|
||||
EXPECT_THROW(err.verify<string>(), string);
|
||||
try {
|
||||
err.verify<string>();
|
||||
} catch (string str) {
|
||||
EXPECT_EQ("error", str);
|
||||
}
|
||||
|
||||
EXPECT_THROW(err.verify<string>("really ", "bad "), string);
|
||||
try {
|
||||
err.verify<string>("really ", "bad ");
|
||||
} catch (string str) {
|
||||
EXPECT_EQ("really bad error", str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Maybe, equalValue)
|
||||
{
|
||||
Maybe<int> a=1, b=1, c=2;
|
||||
EXPECT_TRUE (a==b);
|
||||
EXPECT_FALSE(a==c);
|
||||
EXPECT_TRUE (a!=c);
|
||||
EXPECT_FALSE(a!=b);
|
||||
}
|
||||
|
||||
TEST(Maybe, equalError)
|
||||
{
|
||||
Maybe<char, int> a=genError(1), b=genError(1), c=genError(2);
|
||||
EXPECT_TRUE (a==b);
|
||||
EXPECT_FALSE(a==c);
|
||||
EXPECT_TRUE (a!=c);
|
||||
EXPECT_FALSE(a!=b);
|
||||
|
||||
Maybe<int> d=genError("error1");
|
||||
Maybe<int> e=genError("error2");
|
||||
|
||||
EXPECT_FALSE(d==e);
|
||||
}
|
||||
|
||||
|
||||
class MaybeAssignments : public Test
|
||||
{
|
||||
public:
|
||||
// A class to use as a value.
|
||||
// Maybe runs constructors and destructors manually, so we verify that they're
|
||||
// called properly.
|
||||
class MyValue
|
||||
{
|
||||
public:
|
||||
MyValue(int _x) : x(_x) { addObj(this); }
|
||||
MyValue(const MyValue &other) : x(other.x) { addObj(this); }
|
||||
~MyValue() { delObj(this); }
|
||||
bool operator==(const MyValue &other) const { return x==other.x; }
|
||||
bool operator!=(const MyValue &other) const { return x!=other.x; }
|
||||
|
||||
int x;
|
||||
|
||||
// Tracking all existing objects
|
||||
static set<const MyValue *> objects;
|
||||
|
||||
static void
|
||||
addObj(const MyValue *obj)
|
||||
{
|
||||
EXPECT_EQ(objects.end(), objects.find(obj));
|
||||
objects.insert(obj);
|
||||
}
|
||||
|
||||
static void
|
||||
delObj(const MyValue *obj)
|
||||
{
|
||||
EXPECT_NE(objects.end(), objects.find(obj));
|
||||
objects.erase(obj);
|
||||
}
|
||||
};
|
||||
|
||||
MaybeAssignments()
|
||||
{
|
||||
MyValue::objects.clear();
|
||||
}
|
||||
|
||||
~MaybeAssignments()
|
||||
{
|
||||
EXPECT_THAT(MyValue::objects, IsEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
set<const MaybeAssignments::MyValue *> MaybeAssignments::MyValue::objects;
|
||||
|
||||
// Testing assignment of a new value. Combinations:
|
||||
// Old is value / error
|
||||
// New is value / error
|
||||
// New is L-value / R-value
|
||||
|
||||
TEST_F(MaybeAssignments, ValValRval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(MyValue(1));
|
||||
|
||||
// Change the value
|
||||
EXPECT_EQ(1, m->x);
|
||||
m = 2;
|
||||
EXPECT_EQ(2, m->x);
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ValValLval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(MyValue(1));
|
||||
|
||||
// Change the value
|
||||
EXPECT_EQ(1, m->x);
|
||||
MyValue v = 2;
|
||||
m = v;
|
||||
EXPECT_EQ(2, m->x);
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ErrValRval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(genError(404));
|
||||
|
||||
// Convert an error to a value
|
||||
EXPECT_EQ(MyValue(404), m.getErr());
|
||||
m = 3;
|
||||
EXPECT_EQ(3, m->x);
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ErrValLval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(genError(404));
|
||||
|
||||
// Convert an error to a value
|
||||
EXPECT_EQ(MyValue(404), m.getErr());
|
||||
MyValue v = 3;
|
||||
m = v;
|
||||
EXPECT_EQ(3, m->x);
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ValErrRval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(MyValue(1));
|
||||
|
||||
// Convert a value to an error
|
||||
EXPECT_EQ(1, m->x);
|
||||
m = genError(500);
|
||||
EXPECT_EQ(MyValue(500), m.getErr());
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ValErrLval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(MyValue(1));
|
||||
|
||||
// Convert a value to an error
|
||||
EXPECT_EQ(1, m->x);
|
||||
Error<MyValue> e(500);
|
||||
m = e;
|
||||
EXPECT_EQ(MyValue(500), m.getErr());
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ErrErrRval)
|
||||
{
|
||||
Maybe<uint> m(genError("404"));
|
||||
|
||||
// Change the error
|
||||
EXPECT_EQ("404", m.getErr());
|
||||
Error<string> e("500");
|
||||
m = move(e);
|
||||
EXPECT_EQ("500", m.getErr());
|
||||
}
|
||||
|
||||
TEST_F(MaybeAssignments, ErrErrLval)
|
||||
{
|
||||
Maybe<MyValue, MyValue> m(genError(404));
|
||||
|
||||
// Change the error
|
||||
EXPECT_EQ(MyValue(404), m.getErr());
|
||||
Error<MyValue> e(500);
|
||||
m = e;
|
||||
EXPECT_EQ(MyValue(500), m.getErr());
|
||||
}
|
||||
|
||||
|
||||
class ErrorTranslator
|
||||
{
|
||||
public:
|
||||
template <typename Err>
|
||||
ErrorTranslator(const map<Err, string> &m, Err err) : str(m.at(err)) {}
|
||||
|
||||
operator string() { return str; }
|
||||
|
||||
private:
|
||||
string str;
|
||||
};
|
||||
|
||||
TEST(Maybe, diff_aggr)
|
||||
{
|
||||
Maybe<int, int> err = genError(8);
|
||||
map<int, string> trans = { { 8, "my error" } };
|
||||
try {
|
||||
err.verify<string, ErrorTranslator>(trans);
|
||||
} catch (string str) {
|
||||
EXPECT_EQ("my error", str);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Maybe, illegal_access)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
Maybe<int> err = genError("error");
|
||||
EXPECT_DEATH(*err, "Maybe value is not set");
|
||||
EXPECT_DEATH(err.unpack(), "Maybe value is not set");
|
||||
|
||||
Maybe<int> res = 5;
|
||||
EXPECT_DEATH(res.getErr(), "Maybe value is set");
|
||||
}
|
||||
|
||||
TEST(Maybe, passing_error)
|
||||
{
|
||||
Maybe<int> err1 = genError("error");
|
||||
Maybe<string> err2 = err1.passErr();
|
||||
|
||||
EXPECT_THAT(err2, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(Maybe, maybe_void)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
|
||||
Maybe<void> res;
|
||||
EXPECT_TRUE(res.ok());
|
||||
|
||||
EXPECT_DEATH(res.getErr(), "Maybe value is set");
|
||||
}
|
||||
|
||||
|
||||
TEST(Maybe, maybe_void_error)
|
||||
{
|
||||
Maybe<void> err = genError("error");
|
||||
|
||||
EXPECT_THAT(err, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(Maybe, maybe_void_error_passing)
|
||||
{
|
||||
Maybe<int> err1 = genError("error");
|
||||
Maybe<void> err2 = err1.passErr();
|
||||
|
||||
EXPECT_FALSE(err2.ok());
|
||||
EXPECT_EQ("error", err2.getErr());
|
||||
}
|
||||
|
||||
TEST(Maybe, printing)
|
||||
{
|
||||
ostringstream os;
|
||||
Maybe<int> val1 = 5;
|
||||
os << val1;
|
||||
EXPECT_EQ("Value(5)", os.str());
|
||||
|
||||
os.str("");
|
||||
Maybe<void> val2;
|
||||
os << val2;
|
||||
EXPECT_EQ("Value()", os.str());
|
||||
|
||||
os.str("");
|
||||
Maybe<int> err1 = genError("error");
|
||||
os << err1;
|
||||
EXPECT_EQ("Error(error)", os.str());
|
||||
|
||||
os.str("");
|
||||
Maybe<void> err2 = genError("error");;
|
||||
os << err2;
|
||||
EXPECT_EQ("Error(error)", os.str());
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST(Maybe, cast_value)
|
||||
{
|
||||
Maybe<int> val1 = 5;
|
||||
Maybe<double> val2(val1);
|
||||
EXPECT_THAT(val2, IsValue(5.0));
|
||||
}
|
||||
|
||||
TEST(Maybe, cast_err)
|
||||
{
|
||||
Maybe<string, int> err1 = genError(3);
|
||||
Maybe<string, double> err2 = move(err1);
|
||||
EXPECT_THAT(err2, IsError(3.0));
|
||||
}
|
||||
|
||||
TEST(Maybe, cast_err_void)
|
||||
{
|
||||
Maybe<int, int> err1 = genError(3);
|
||||
Maybe<double, void> err2 = err1;
|
||||
EXPECT_FALSE(err2.ok());
|
||||
}
|
||||
42
core/core_ut/tostring_ut.cc
Normal file
42
core/core_ut/tostring_ut.cc
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "tostring.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
TEST(ToStringTest, basic)
|
||||
{
|
||||
ToString str;
|
||||
EXPECT_EQ("", static_cast<string>(str));
|
||||
|
||||
string tmp = str;
|
||||
EXPECT_EQ("", tmp);
|
||||
}
|
||||
|
||||
TEST(ToStringTest, one_parameter)
|
||||
{
|
||||
ToString str("aaa");
|
||||
EXPECT_EQ(string("aaa"), static_cast<string>(str));
|
||||
}
|
||||
|
||||
TEST(ToStringTest, three_parameters)
|
||||
{
|
||||
ToString str("R", 8, 0);
|
||||
EXPECT_EQ(string("R80"), static_cast<string>(str));
|
||||
}
|
||||
|
||||
TEST(ToStringTest, operator)
|
||||
{
|
||||
ToString str;
|
||||
str << 'R' << 80;
|
||||
EXPECT_EQ(string("R80"), static_cast<string>(str));
|
||||
}
|
||||
|
||||
TEST(ToStringTest, reset)
|
||||
{
|
||||
ToString str("aaa");
|
||||
EXPECT_EQ(string("aaa"), static_cast<string>(str));
|
||||
str.reset();
|
||||
EXPECT_EQ(string(), static_cast<string>(str));
|
||||
}
|
||||
116
core/core_ut/virtual_container_ut.cc
Normal file
116
core/core_ut/virtual_container_ut.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "virtual_modifiers.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TEST(SingleModifer, CharRemover)
|
||||
{
|
||||
string orig = " 123 45 67 ggg\t h ";
|
||||
|
||||
auto without_space = makeVirtualContainer<CharRemover<' '>>(orig);
|
||||
auto without_tab = makeVirtualContainer<CharRemover<'\t'>>(orig);
|
||||
auto without_g = makeVirtualContainer<CharRemover<'g'>>(orig);
|
||||
auto withall = makeVirtualContainer<CharRemover<'p'>>(orig);
|
||||
|
||||
EXPECT_EQ(orig, " 123 45 67 ggg\t h ");
|
||||
EXPECT_EQ(string(without_space.begin(), without_space.end()), "1234567ggg\th");
|
||||
EXPECT_EQ(string(without_tab.begin(), without_tab.end()), " 123 45 67 ggg h ");
|
||||
EXPECT_EQ(string(without_g.begin(), without_g.end()), " 123 45 67 \t h ");
|
||||
EXPECT_EQ(string(withall.begin(), withall.end()), orig);
|
||||
}
|
||||
|
||||
TEST(SingleModifer, HexDecoder)
|
||||
{
|
||||
string orig = "%45 %46 x47 %4";
|
||||
|
||||
auto decode_cent = makeVirtualContainer<HexDecoder<'%'>>(orig);
|
||||
auto decode_x = makeVirtualContainer<HexDecoder<'x'>>(orig);
|
||||
|
||||
EXPECT_EQ(orig, "%45 %46 x47 %4");
|
||||
EXPECT_EQ(string(decode_cent.begin(), decode_cent.end()), "E F x47 %4");
|
||||
EXPECT_EQ(string(decode_x.begin(), decode_x.end()), "%45 %46 G %4");
|
||||
|
||||
orig = "452e462E47";
|
||||
auto decode_all = makeVirtualContainer<HexDecoder<-1>>(orig);
|
||||
EXPECT_EQ(string(decode_all.begin(), decode_all.end()), "E.F.G");
|
||||
}
|
||||
|
||||
TEST(SingleModifer, ReplaceChar)
|
||||
{
|
||||
string orig = "12+34-56-78+90-12-34+56";
|
||||
|
||||
auto plus_to_space = makeVirtualContainer<ReplaceChar<'+', ' '>>(orig);
|
||||
auto minus_to_plus = makeVirtualContainer<ReplaceChar<'-', '+'>>(orig);
|
||||
auto plus_to_minus = makeVirtualContainer<ReplaceChar<'+', '-'>>(orig);
|
||||
auto minus_to_space = makeVirtualContainer<ReplaceChar<'-', ' '>>(orig);
|
||||
auto m_to_n = makeVirtualContainer<ReplaceChar<'m', 'n'>>(orig);
|
||||
|
||||
EXPECT_EQ(orig, "12+34-56-78+90-12-34+56");
|
||||
EXPECT_EQ(string(plus_to_space.begin(), plus_to_space.end()), "12 34-56-78 90-12-34 56");
|
||||
EXPECT_EQ(string(minus_to_plus.begin(), minus_to_plus.end()), "12+34+56+78+90+12+34+56");
|
||||
EXPECT_EQ(string(plus_to_minus.begin(), plus_to_minus.end()), "12-34-56-78-90-12-34-56");
|
||||
EXPECT_EQ(string(minus_to_space.begin(), minus_to_space.end()), "12+34 56 78+90 12 34+56");
|
||||
EXPECT_EQ(string(m_to_n.begin(), m_to_n.end()), orig);
|
||||
}
|
||||
|
||||
class ReplaceOne : public ReplaceSubContiners<string>
|
||||
{
|
||||
public:
|
||||
ReplaceOne() { init(&src, &dst); }
|
||||
|
||||
private:
|
||||
string src = "111";
|
||||
string dst = "222";
|
||||
};
|
||||
|
||||
class ReplaceTwo : public ReplaceSubContiners<string>
|
||||
{
|
||||
public:
|
||||
ReplaceTwo() { init(&src, &dst); }
|
||||
|
||||
private:
|
||||
string src = "333";
|
||||
string dst = "4444";
|
||||
};
|
||||
|
||||
class ReplaceThree : public ReplaceSubContiners<string>
|
||||
{
|
||||
public:
|
||||
ReplaceThree() { init(&src, &dst); }
|
||||
|
||||
private:
|
||||
string src = "555";
|
||||
string dst = "44";
|
||||
};
|
||||
|
||||
TEST(SingleModifer, ReplaceSubContiners)
|
||||
{
|
||||
string orig = "111 333 11 555 1111";
|
||||
|
||||
auto replace_one = makeVirtualContainer<ReplaceOne>(orig);
|
||||
auto replace_two = makeVirtualContainer<ReplaceTwo>(orig);
|
||||
auto replace_three = makeVirtualContainer<ReplaceThree>(orig);
|
||||
|
||||
EXPECT_EQ(orig, "111 333 11 555 1111");
|
||||
EXPECT_EQ(string(replace_one.begin(), replace_one.end()), "222 333 11 555 2221");
|
||||
EXPECT_EQ(string(replace_two.begin(), replace_two.end()), "111 4444 11 555 1111");
|
||||
EXPECT_EQ(string(replace_three.begin(), replace_three.end()), "111 333 11 44 1111");
|
||||
}
|
||||
|
||||
TEST(MultipleModifier, TwoModifiers)
|
||||
{
|
||||
string orig = " 4 5 2 e 4 6 2 E 4 7 ";
|
||||
auto decode_one = makeVirtualContainer<CharRemover<' '>>(orig);
|
||||
auto decode_two = makeVirtualContainer<HexDecoder<-1>>(decode_one);
|
||||
EXPECT_EQ(string(decode_two.begin(), decode_two.end()), "E.F.G");
|
||||
}
|
||||
|
||||
using CombinedModifier = ModifiersAggregator<HexDecoder<-1>, CharRemover<' '>>;
|
||||
|
||||
TEST(MultipleModifier, CombinedModifier)
|
||||
{
|
||||
string orig = " 4 5 2 e 4 6 2 E 4 7 ";
|
||||
auto decode = makeVirtualContainer<CombinedModifier>(orig);
|
||||
EXPECT_EQ(string(decode.begin(), decode.end()), "E.F.G");
|
||||
}
|
||||
10
core/cptest/CMakeLists.txt
Normal file
10
core/cptest/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
if("${PLATFORM_TYPE}" MATCHES "arm.*")
|
||||
ADD_DEFINITIONS(-Wno-sign-compare)
|
||||
elseif("${PLATFORM_TYPE}" STREQUAL "alpine")
|
||||
ADD_DEFINITIONS(-Wno-deprecated-copy)
|
||||
endif()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/components/include)
|
||||
include_directories(${gtest_INCLUDE_DIRS})
|
||||
add_library(cptest cptest.cc cptest_data_buf.cc cptest_tcppacket.cc)
|
||||
|
||||
add_subdirectory(cptest_ut)
|
||||
145
core/cptest/cptest.cc
Executable file
145
core/cptest/cptest.cc
Executable file
@@ -0,0 +1,145 @@
|
||||
#include "cptest.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
cptestPrepareToDie()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&std::cerr); // EXPECT_DEATH checks cerror for messages, so send them there.
|
||||
}
|
||||
|
||||
CPTestTempfile::CPTestTempfile(const std::vector<std::string> &lines)
|
||||
{
|
||||
// Create the file
|
||||
char temp_file_template[] = "/tmp/cptest_temp_file_XXXXXX";
|
||||
int fd = mkstemp(temp_file_template);
|
||||
if (fd < 0) {
|
||||
// LCOV_EXCL_START - mkstemp rarely fails, can't cause it.
|
||||
ADD_FAILURE() << "Failed to open tempfile";
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
fname = temp_file_template; // Template was modified to actual file name
|
||||
|
||||
// Fill it with the requested lines
|
||||
for (const auto &l : lines) {
|
||||
EXPECT_EQ((unsigned long)write(fd, l.c_str(), l.size()), l.size());
|
||||
EXPECT_EQ(write(fd, "\n", 1), 1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
CPTestTempfile::CPTestTempfile()
|
||||
:
|
||||
CPTestTempfile(std::vector<std::string>{})
|
||||
{
|
||||
}
|
||||
|
||||
CPTestTempfile::~CPTestTempfile()
|
||||
{
|
||||
if (!fname.empty()) {
|
||||
unlink(fname.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
CPTestTempfile::readFile() const
|
||||
{
|
||||
int fd = open(fname.c_str(), 0);
|
||||
EXPECT_NE(fd, -1);
|
||||
|
||||
string result;
|
||||
char buf[100];
|
||||
while (true) {
|
||||
auto bytes_read = read(fd, buf, 100);
|
||||
if (bytes_read <= 0) break;
|
||||
result += string(buf, bytes_read);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse Hex data, e.g. what's generated by "tcpdump -xx", into a vector
|
||||
vector<u_char>
|
||||
cptestParseHex(const string &hex_text)
|
||||
{
|
||||
vector<u_char> v;
|
||||
|
||||
// Use stringstream and istream_iterator to break the input into whitespace separated strings
|
||||
stringstream str(hex_text);
|
||||
for (auto it = istream_iterator<string>(str); it != istream_iterator<string>(); it++) {
|
||||
const string &t = *it;
|
||||
size_t l = t.size();
|
||||
if (l==0) continue;
|
||||
if (t[l-1]==':') continue; // tcpdump uses xxxx: to mark offsets, not data. So ignore it.
|
||||
dbgAssert(t.size() %2 == 0) << "Expecting an even number of hex digits, " << t << " is invalid";
|
||||
for (uint i=0; i<t.size()/2; i++) {
|
||||
u_char n = strtoul(t.substr(i*2, 2).c_str(), nullptr, 16);
|
||||
v.push_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// The inverse of cptest_parse_hex
|
||||
// Take a vector of data, and generate hex from it output, like tcpdump.
|
||||
std::string
|
||||
cptestGenerateHex(const std::vector<u_char> &vec, bool print_offsets)
|
||||
{
|
||||
std::string res;
|
||||
|
||||
int total_hex_groups_emitted = 0;
|
||||
int num_hex_groups_emitted_in_cur_line = 0;
|
||||
for (size_t i = 0; i<vec.size(); i++) {
|
||||
if (num_hex_groups_emitted_in_cur_line == 16) {
|
||||
res += "\n";
|
||||
num_hex_groups_emitted_in_cur_line = 0;
|
||||
}
|
||||
if (print_offsets && (num_hex_groups_emitted_in_cur_line == 0)) {
|
||||
char offset_str[12];
|
||||
snprintf(offset_str, sizeof(offset_str), "%04x: ", total_hex_groups_emitted);
|
||||
res += offset_str;
|
||||
}
|
||||
|
||||
char cur_char_as_hex[10];
|
||||
snprintf(cur_char_as_hex, sizeof(cur_char_as_hex), "%02x ", vec[i]);
|
||||
res += cur_char_as_hex;
|
||||
num_hex_groups_emitted_in_cur_line++;
|
||||
total_hex_groups_emitted++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get the path to a file, which is in the same directory as the binary.
|
||||
std::string
|
||||
cptestFnameInExeDir(const std::string &name)
|
||||
{
|
||||
auto bin_path = ::testing::internal::GetArgvs()[0]; // Internal ugly API.
|
||||
auto slash = bin_path.rfind('/');
|
||||
if (slash==string::npos) {
|
||||
// bin_path contains no dir. So return name with no dir
|
||||
// LCOV_EXCL_START - Our unit tests always run from an absolute path
|
||||
return name;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto bin_dir = bin_path.substr(0, slash);
|
||||
return bin_dir + "/" + name;
|
||||
}
|
||||
|
||||
string
|
||||
cptestFnameInSrcDir(const string &name)
|
||||
{
|
||||
char *path = getenv("CURR_SRC_DIR");
|
||||
if (path == nullptr) return name;
|
||||
return std::string(path) + "/" + name;
|
||||
}
|
||||
48
core/cptest/cptest_data_buf.cc
Executable file
48
core/cptest/cptest_data_buf.cc
Executable file
@@ -0,0 +1,48 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string
|
||||
formatNum(const char *fmt, int n)
|
||||
{
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), fmt, n);
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const Buffer &buf)
|
||||
{
|
||||
auto len = buf.size();
|
||||
auto data = buf.data();
|
||||
const int line_chars = 16;
|
||||
os << "Buffer Data:" << endl;
|
||||
for (uint i = 0; i<(len+line_chars-1)/line_chars; i++) {
|
||||
// Line header
|
||||
os << formatNum("%04x", i*line_chars) << ": ";
|
||||
|
||||
// Hex of each character
|
||||
for (uint j = 0; j<line_chars; j++) {
|
||||
uint pos = i*line_chars + j;
|
||||
os << " " << (pos<len ? formatNum("%02x", data[pos]) : " ");
|
||||
}
|
||||
|
||||
os << " ";
|
||||
|
||||
// Printable chars
|
||||
for (uint j = 0; j<line_chars; j++) {
|
||||
uint pos = i*line_chars + j;
|
||||
if (pos >= len) break;
|
||||
os << (isprint(data[pos]) ? char(data[pos]) : '.');
|
||||
}
|
||||
|
||||
os << endl;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
593
core/cptest/cptest_tcppacket.cc
Executable file
593
core/cptest/cptest_tcppacket.cc
Executable file
@@ -0,0 +1,593 @@
|
||||
#include "cptest/cptest_tcppacket.h"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "c_common/network_defs.h"
|
||||
#include "packet.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_STREAMING);
|
||||
|
||||
//
|
||||
// Append some data - various overloads - to a u_char vector
|
||||
//
|
||||
|
||||
void
|
||||
vec_append(vector<u_char> &target, const void *data, uint len)
|
||||
{
|
||||
auto p = reinterpret_cast<const u_char *>(data);
|
||||
target.insert(target.end(), p, p + len);
|
||||
}
|
||||
|
||||
void
|
||||
vec_append(vector<u_char> &target, uint num)
|
||||
{
|
||||
vec_append(target, &num, sizeof(num));
|
||||
}
|
||||
|
||||
void
|
||||
vec_append(vector<u_char> &target, const vector<u_char> &source)
|
||||
{
|
||||
target.insert(target.end(), source.begin(), source.end());
|
||||
}
|
||||
|
||||
//
|
||||
// TCP Option generation
|
||||
//
|
||||
|
||||
class TCPOption::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(const string &_name, const vector<u_char> &_data);
|
||||
|
||||
const string name;
|
||||
const vector<u_char> data;
|
||||
};
|
||||
|
||||
|
||||
TCPOption::Impl::Impl(const string &_name, const vector<u_char> &_data)
|
||||
:
|
||||
name(_name),
|
||||
data(_data)
|
||||
{
|
||||
}
|
||||
|
||||
TCPOption::TCPOption(const string &_name, const vector<u_char> _data)
|
||||
:
|
||||
pimpl(make_unique<Impl>(_name, _data))
|
||||
{
|
||||
}
|
||||
|
||||
TCPOption::TCPOption(const TCPOption &from)
|
||||
:
|
||||
pimpl(make_unique<Impl>(*from.pimpl))
|
||||
{
|
||||
}
|
||||
|
||||
TCPOption::~TCPOption()
|
||||
{
|
||||
}
|
||||
|
||||
size_t
|
||||
TCPOption::size() const
|
||||
{
|
||||
return pimpl->data.size();
|
||||
}
|
||||
|
||||
vector<u_char>
|
||||
TCPOption::build() const
|
||||
{
|
||||
return pimpl->data;
|
||||
}
|
||||
|
||||
const TCPOption TCPOption::NOP("NOP", { 1 }); // Type 1, no length (exceptional)
|
||||
const TCPOption TCPOption::SACK_PERMITTED("sack permitted", { 4, 2 }); // Type 4, length 2
|
||||
|
||||
TCPOption
|
||||
TCPOption::windowScaling(u_char shift_count)
|
||||
{
|
||||
// Type 3, length 3, data = shift
|
||||
return TCPOption("window scaling", { 3, 3, shift_count });
|
||||
}
|
||||
|
||||
TCPOption
|
||||
TCPOption::timeStamp(uint value, uint echo_reply)
|
||||
{
|
||||
vector<u_char> data { 8, 0 }; // Type 8, size set below
|
||||
vec_append(data, htonl(value));
|
||||
vec_append(data, htonl(echo_reply));
|
||||
data[1] = data.size();
|
||||
return TCPOption("timestamp", data);
|
||||
}
|
||||
|
||||
TCPOption
|
||||
TCPOption::selectiveACK(const vector<pair<uint, uint>> &edges)
|
||||
{
|
||||
vector<u_char> data { 5, 0 }; // Type 8, size set below
|
||||
for (auto const &edge : edges) {
|
||||
vec_append(data, htonl(edge.first));
|
||||
vec_append(data, htonl(edge.second));
|
||||
}
|
||||
data[1] = data.size();
|
||||
return TCPOption("sack", data);
|
||||
}
|
||||
|
||||
// Append a TCP option to a u_char vector
|
||||
void
|
||||
vec_append(vector<u_char> &target, const TCPOption &source)
|
||||
{
|
||||
vec_append(target, source.build());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Checksum calculation
|
||||
// This is NOT an efficient implementation. It's used because it is straight-forward.
|
||||
// Also, it's not the same as in the streamer, so we test the streamer's algorithm.
|
||||
//
|
||||
|
||||
static uint16_t
|
||||
bufCSumSimple(const u_char *buff, uint length)
|
||||
{
|
||||
uint32_t acc = 0xffff;
|
||||
|
||||
// Handle complete 16-bit blocks.
|
||||
for (size_t i = 0; i+1<length; i += 2) {
|
||||
uint16_t word;
|
||||
memcpy(&word, buff + i, 2);
|
||||
acc += ntohs(word);
|
||||
if (acc > 0xffff) acc -= 0xffff;
|
||||
}
|
||||
|
||||
// Handle any partial block at the end of the data.
|
||||
if ((length % 2) == 1) {
|
||||
uint16_t word = 0;
|
||||
memcpy(&word, buff + length - 1, 1);
|
||||
acc += ntohs(word);
|
||||
if (acc > 0xffff) acc -= 0xffff;
|
||||
}
|
||||
|
||||
return ~static_cast<uint16_t>(acc);
|
||||
}
|
||||
|
||||
// TCP checksum, generic v4/v6 version
|
||||
static uint16_t
|
||||
tcpCSum(const vector<u_char> &pseudo_pkt_header, const u_char *tcp, uint total_tcplen)
|
||||
{
|
||||
// Finish building the packet after having the pssudo header
|
||||
auto pseudo_header_size = pseudo_pkt_header.size();
|
||||
auto pseudo_pkt = pseudo_pkt_header;
|
||||
vec_append(pseudo_pkt, tcp, total_tcplen);
|
||||
|
||||
// Set the pseudo packet's TCP checksum to 0, so it won't be included in the calculation
|
||||
auto pseudo_tcp = reinterpret_cast<struct TcpHdr *>(&pseudo_pkt[pseudo_header_size]);
|
||||
pseudo_tcp->check = 0;
|
||||
|
||||
return bufCSumSimple(pseudo_pkt.data(), pseudo_pkt.size());
|
||||
}
|
||||
|
||||
// This isn't efficient in the calculaiton of the psuedo header, and is not suitable for general use!
|
||||
static uint16_t
|
||||
tcpV4CSum(const struct ip *ip)
|
||||
{
|
||||
auto tcp_buf = reinterpret_cast<const u_char *>(ip) + (ip->ip_hl * 4);
|
||||
uint iplen = ntohs(ip->ip_len);
|
||||
uint total_tcplen = iplen - (ip->ip_hl * 4);
|
||||
// Build a psuedo IP header for the calcualtion of the TCP checksum
|
||||
vector<u_char> pseudo_pkt;
|
||||
vec_append(pseudo_pkt, &(ip->ip_src), sizeof(ip->ip_src));
|
||||
vec_append(pseudo_pkt, &(ip->ip_dst), sizeof(ip->ip_dst));
|
||||
uint16_t ipproto = htons(IPPROTO_TCP);
|
||||
vec_append(pseudo_pkt, &(ipproto), sizeof(ipproto));
|
||||
uint16_t len = htons(total_tcplen);
|
||||
vec_append(pseudo_pkt, &len, sizeof(len));
|
||||
|
||||
return tcpCSum(pseudo_pkt, tcp_buf, total_tcplen);
|
||||
}
|
||||
|
||||
// This isn't efficient in the calculaiton of the psuedo header, and is not suitable for general use!
|
||||
static uint16_t
|
||||
tcpV6CSum(const struct ip6_hdr *ip6)
|
||||
{
|
||||
auto tcp_buf = reinterpret_cast<const u_char *>(ip6) + sizeof(*ip6);
|
||||
uint total_tcplen = ntohs(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen); // Why so simple?
|
||||
// Build a psuedo IP header for the calcualtion of the TCP checksum
|
||||
vector<u_char> pseudo_pkt;
|
||||
vec_append(pseudo_pkt, &(ip6->ip6_src), sizeof(ip6->ip6_src));
|
||||
vec_append(pseudo_pkt, &(ip6->ip6_dst), sizeof(ip6->ip6_dst));
|
||||
uint16_t ipproto = htons(IPPROTO_TCP);
|
||||
vec_append(pseudo_pkt, &(ipproto), sizeof(ipproto));
|
||||
uint16_t len = htons(total_tcplen);
|
||||
vec_append(pseudo_pkt, &(len), sizeof(len));
|
||||
|
||||
return tcpCSum(pseudo_pkt, tcp_buf, total_tcplen);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
ipv4_csum(const struct ip *ip)
|
||||
{
|
||||
// Copy the IP header aside
|
||||
const u_char *ip_p = reinterpret_cast<const u_char *>(ip);
|
||||
vector<u_char> ip_copy(ip_p, ip_p+(ip->ip_hl * 4));
|
||||
auto ip_copy_p = reinterpret_cast<struct ip *>(ip_copy.data());
|
||||
|
||||
// Set the checksum to 0 so it won't be included in the calculation
|
||||
ip_copy_p->ip_sum = 0;
|
||||
|
||||
// Calculate the checksum
|
||||
return bufCSumSimple(ip_copy.data(), ip_copy.size());
|
||||
}
|
||||
|
||||
//
|
||||
// TCP packet generation
|
||||
// This code can generate various TCP packets, for testing purposes
|
||||
//
|
||||
|
||||
class TCPPacket::Impl
|
||||
{
|
||||
public:
|
||||
Impl(CDir _cdir);
|
||||
unique_ptr<Packet> build(const ConnKey &ck) const;
|
||||
|
||||
uint tcp_seq = 1200; // Arbitrary
|
||||
uint tcp_ack = 3300000; // Arbitrary
|
||||
uint16_t tcp_window = 4096; // Reasonable
|
||||
string tcp_flags = "A"; // Default to simple ACK
|
||||
bool tcp_cksum_auto = true; // Auto checksum by default
|
||||
uint16_t tcp_cksum_override;
|
||||
uint16_t tcp_urgent_ptr = 0;
|
||||
uint tcp_header_size = sizeof(struct TcpHdr);
|
||||
int tcp_data_offset = -1;
|
||||
|
||||
vector<u_char> l2_header;
|
||||
vector<u_char> tcp_payload;
|
||||
vector<TCPOption> tcp_options;
|
||||
|
||||
private:
|
||||
bool has_tcp_flag(char letter) const;
|
||||
uint l4_hdr_len() const;
|
||||
|
||||
// Methods to build the packet data, step by step
|
||||
vector<u_char> build_pkt_bytes(const ConnKey &key) const;
|
||||
void emit_l2_hdr (vector<u_char> &pkt) const;
|
||||
void emit_l3_hdr (vector<u_char> &pkt, const ConnKey &ck) const;
|
||||
void emit_l4_hdr (vector<u_char> &pkt, const ConnKey &ck) const;
|
||||
void emit_tcp_options(vector<u_char> &pkt) const;
|
||||
void emit_payload (vector<u_char> &pkt) const;
|
||||
void fixup_l4_cksum (vector<u_char> &pkt, IPType type) const;
|
||||
void fixup_l3_cksum (vector<u_char> &pkt, IPType type) const;
|
||||
|
||||
CDir cdir;
|
||||
};
|
||||
|
||||
TCPPacket::Impl::Impl(CDir _cdir)
|
||||
:
|
||||
cdir(_cdir)
|
||||
{
|
||||
}
|
||||
|
||||
uint
|
||||
TCPPacket::Impl::l4_hdr_len() const
|
||||
{
|
||||
// Basic length
|
||||
uint sz = sizeof(struct TcpHdr);
|
||||
|
||||
// TCP options
|
||||
for (auto const &opt : tcp_options) {
|
||||
sz += opt.size();
|
||||
}
|
||||
|
||||
// Align to multiple of 4
|
||||
while (sz%4 != 0) {
|
||||
sz++;
|
||||
}
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
bool
|
||||
TCPPacket::Impl::has_tcp_flag(char letter) const
|
||||
{
|
||||
return tcp_flags.find(letter) != string::npos;
|
||||
}
|
||||
|
||||
unique_ptr<Packet>
|
||||
TCPPacket::Impl::build(const ConnKey &ck) const
|
||||
{
|
||||
// Figure out the key to use
|
||||
auto key = ck;
|
||||
if (cdir == CDir::S2C) key.reverse();
|
||||
|
||||
// Build the packet data
|
||||
auto data = build_pkt_bytes(key);
|
||||
|
||||
// Build a Packet, set the conn and cdir
|
||||
auto pkt_type = l2_header.empty() ? PktType::PKT_L3 : PktType::PKT_L2;
|
||||
auto p = Packet::genPacket(pkt_type, key.getType(), data);
|
||||
|
||||
if (!p.ok()) {
|
||||
dbgError(D_STREAMING) << "Failed to build packet for " << key << " err=" << (int)p.getErr() <<
|
||||
" payload: " << Buffer(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
(*p)->setCDir(cdir);
|
||||
|
||||
return p.unpackMove();
|
||||
}
|
||||
|
||||
vector<u_char>
|
||||
TCPPacket::Impl::build_pkt_bytes(const ConnKey &key) const
|
||||
{
|
||||
vector<u_char> data;
|
||||
|
||||
emit_l3_hdr(data, key);
|
||||
emit_l4_hdr(data, key);
|
||||
emit_payload(data);
|
||||
fixup_l4_cksum(data, key.getType());
|
||||
fixup_l3_cksum(data, key.getType());
|
||||
emit_l2_hdr(data); // Insert l2_hdr at the beginning.
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::emit_l2_hdr(vector<u_char> &pkt) const
|
||||
{
|
||||
// We use 'insert' and not vec_append because l2 header should be at the beginning.
|
||||
pkt.insert(pkt.begin(), l2_header.begin(), l2_header.end());
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::emit_l3_hdr(vector<u_char> &pkt, const ConnKey &ck) const
|
||||
{
|
||||
uint payload_length = l4_hdr_len() + tcp_payload.size();
|
||||
|
||||
if (ck.getType() == IPType::V4) {
|
||||
struct ip iphdr = {
|
||||
ip_hl : 5,
|
||||
ip_v : 4,
|
||||
ip_tos : 0,
|
||||
ip_len : htons(sizeof(struct ip) + payload_length),
|
||||
ip_id : htons(7766),
|
||||
ip_off : htons(0x4000), // flags + offset. flags set to don't fragment
|
||||
ip_ttl : 64,
|
||||
ip_p : IPPROTO_TCP,
|
||||
ip_sum : 0, // will be fixed by fixup_l3_cksum
|
||||
ip_src : ck.getSrc().getIPv4(), // already in network order in the ck
|
||||
ip_dst : ck.getDst().getIPv4(), // already in network order in the ck
|
||||
};
|
||||
vec_append(pkt, &iphdr, sizeof(iphdr));
|
||||
} else {
|
||||
struct ip6_hdr ip6hdr;
|
||||
|
||||
// The IPv6 header is simple. Linux's headers, however, are like this:
|
||||
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_flow = 0;
|
||||
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payload_length);
|
||||
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_TCP;
|
||||
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_hlim = 123;
|
||||
ip6hdr.ip6_ctlun.ip6_un2_vfc = 0x60; // Overwrites part of ip6_un1_flow
|
||||
ip6hdr.ip6_src = ck.getSrc().getIPv6();
|
||||
ip6hdr.ip6_dst = ck.getDst().getIPv6();
|
||||
|
||||
vec_append(pkt, &ip6hdr, sizeof(ip6hdr));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::emit_l4_hdr(vector<u_char> &pkt, const ConnKey &ck) const
|
||||
{
|
||||
// Basic header
|
||||
struct TcpHdr tcp;
|
||||
tcp.source = htons(ck.getSPort());
|
||||
tcp.dest = htons(ck.getDPort());
|
||||
tcp.seq = htonl(tcp_seq);
|
||||
tcp.ack_seq = htonl(tcp_ack);
|
||||
tcp.res1 = 0; // unused 4 bits
|
||||
tcp.doff = static_cast<u_char>(tcp_data_offset > -1 ? tcp_data_offset
|
||||
: l4_hdr_len() / 4);
|
||||
tcp.fin = has_tcp_flag('F');
|
||||
tcp.syn = has_tcp_flag('S');
|
||||
tcp.rst = has_tcp_flag('R');
|
||||
tcp.psh = has_tcp_flag('P');
|
||||
tcp.ack = has_tcp_flag('A');
|
||||
tcp.urg = has_tcp_flag('U');
|
||||
tcp.res2 = 0; // ECE and CWR. Never mind them.
|
||||
tcp.window = htons(tcp_window);
|
||||
tcp.check = 0; // will be fixed by fixup_l4_cksum
|
||||
tcp.urg_ptr = htons(tcp_urgent_ptr);
|
||||
|
||||
vec_append(pkt, &tcp, tcp_header_size);
|
||||
|
||||
// TCP Options
|
||||
emit_tcp_options(pkt);
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::emit_tcp_options(vector<u_char> &pkt) const
|
||||
{
|
||||
// Concatenate options in a vector, then append with NOPs to multiple of 4
|
||||
vector<u_char> optbuf;
|
||||
|
||||
for (auto const &opt : tcp_options) {
|
||||
vec_append(optbuf, opt);
|
||||
}
|
||||
while (optbuf.size()%4 != 0) {
|
||||
vec_append(optbuf, TCPOption::NOP);
|
||||
}
|
||||
dbgAssert(optbuf.size() <= 40) << "too many tcp options. max is 40 bytes";
|
||||
|
||||
vec_append(pkt, optbuf);
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::emit_payload(vector<u_char> &pkt) const
|
||||
{
|
||||
vec_append(pkt, tcp_payload);
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::fixup_l4_cksum(vector<u_char> &pkt, IPType type) const
|
||||
{
|
||||
u_char *l3 = pkt.data();
|
||||
if (type == IPType::V4) {
|
||||
if (pkt.size() < sizeof(struct ip) + sizeof(struct TcpHdr)) return;
|
||||
auto ip = reinterpret_cast<struct ip *>(l3);
|
||||
auto tcp = reinterpret_cast<struct TcpHdr *>(l3 + (ip->ip_hl*4));
|
||||
tcp->check = htons(tcp_cksum_auto ? tcpV4CSum(ip) : tcp_cksum_override);
|
||||
} else {
|
||||
if (pkt.size() < sizeof(struct ip6_hdr) + sizeof(struct TcpHdr)) return;
|
||||
auto ip6 = reinterpret_cast<struct ip6_hdr *>(l3);
|
||||
auto tcp = reinterpret_cast<struct TcpHdr *>(l3 + sizeof(*ip6));
|
||||
tcp->check = htons(tcp_cksum_auto ? tcpV6CSum(ip6) : tcp_cksum_override);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TCPPacket::Impl::fixup_l3_cksum(vector<u_char> &pkt, IPType type) const
|
||||
{
|
||||
if (type == IPType::V4) {
|
||||
auto ip = reinterpret_cast<struct ip *>(pkt.data());
|
||||
ip->ip_sum = ipv4_csum(ip);
|
||||
} else {
|
||||
// No checksum in IPv6 header. Hurray!
|
||||
}
|
||||
}
|
||||
|
||||
TCPPacket::TCPPacket(CDir _cdir)
|
||||
:
|
||||
pimpl(make_unique<Impl>(_cdir))
|
||||
{
|
||||
}
|
||||
|
||||
TCPPacket::TCPPacket(TCPPacket &&from)
|
||||
:
|
||||
pimpl(std::move(from.pimpl))
|
||||
{
|
||||
}
|
||||
|
||||
TCPPacket::~TCPPacket()
|
||||
{
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPPayload(const vector<u_char> &payload)
|
||||
{
|
||||
pimpl->tcp_payload = payload;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPPayload(const string &payload)
|
||||
{
|
||||
vector<u_char> vec;
|
||||
vec.insert(vec.end(), payload.begin(), payload.end());
|
||||
return setTCPPayload(vec);
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::addTCPOption(const TCPOption &option)
|
||||
{
|
||||
pimpl->tcp_options.push_back(option);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setL4HeaderSize(uint header_size)
|
||||
{
|
||||
pimpl->tcp_header_size = header_size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setL4DataOffset(uint data_offset)
|
||||
{
|
||||
pimpl->tcp_data_offset = data_offset;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPSeq(uint _tcp_seq)
|
||||
{
|
||||
pimpl->tcp_seq = _tcp_seq;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPAck(uint _tcp_ack)
|
||||
{
|
||||
pimpl->tcp_ack = _tcp_ack;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPWindow(uint16_t _tcp_window)
|
||||
{
|
||||
pimpl->tcp_window = _tcp_window;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPFlags(string _tcp_flags)
|
||||
{
|
||||
pimpl->tcp_flags = _tcp_flags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPUrgentPtr(uint16_t _tcp_urgent_ptr)
|
||||
{
|
||||
pimpl->tcp_urgent_ptr = _tcp_urgent_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setTCPCksum(uint _tcp_cksum_override)
|
||||
{
|
||||
pimpl->tcp_cksum_auto = false;
|
||||
pimpl->tcp_cksum_override = _tcp_cksum_override;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TCPPacket &
|
||||
TCPPacket::setL2Header(const vector<u_char> &_l2_header)
|
||||
{
|
||||
pimpl->l2_header = _l2_header;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint
|
||||
TCPPacket::getTCPSeq() const
|
||||
{
|
||||
return pimpl->tcp_seq;
|
||||
}
|
||||
|
||||
unique_ptr<Packet>
|
||||
TCPPacket::build(const ConnKey &ck) const
|
||||
{
|
||||
return pimpl->build(ck);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
TCPPacket::calcTCPv4Checksum(const vector<u_char> &pkt)
|
||||
{
|
||||
auto l3 = pkt.data();
|
||||
auto ip = reinterpret_cast<const struct ip *>(l3);
|
||||
return tcpV4CSum(ip);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
TCPPacket::calcTCPv6Checksum(const vector<u_char> &pkt)
|
||||
{
|
||||
auto l3 = pkt.data();
|
||||
auto ip6 = reinterpret_cast<const struct ip6_hdr *>(l3);
|
||||
return tcpV6CSum(ip6);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
TCPPacket::calcIPv4Checksum(const vector<u_char> &pkt)
|
||||
{
|
||||
auto l3 = pkt.data();
|
||||
auto ip = reinterpret_cast<const struct ip *>(l3);
|
||||
return ipv4_csum(ip);
|
||||
}
|
||||
5
core/cptest/cptest_ut/CMakeLists.txt
Normal file
5
core/cptest/cptest_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
cptest_ut
|
||||
"cptest_ut.cc;cptest_packet_ut.cc"
|
||||
"buffers;packet;connkey;singleton;logging"
|
||||
)
|
||||
222
core/cptest/cptest_ut/cptest_packet_ut.cc
Executable file
222
core/cptest/cptest_ut/cptest_packet_ut.cc
Executable file
@@ -0,0 +1,222 @@
|
||||
#include "cptest/cptest_tcppacket.h"
|
||||
#include <fstream>
|
||||
#include "cptest.h"
|
||||
#include "c_common/network_defs.h"
|
||||
#include "byteorder.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class PacketTest : public Test
|
||||
{
|
||||
public:
|
||||
// Extract TCP options from a packet
|
||||
static Buffer
|
||||
getOptions(const Packet *p)
|
||||
{
|
||||
auto tcpHdr = p->getL4Header();
|
||||
tcpHdr.truncateHead(sizeof(struct TcpHdr));
|
||||
return tcpHdr;
|
||||
}
|
||||
|
||||
ConnKey ck4{IPAddr::createIPAddr("10.0.0.1").unpack(), 1234, IPAddr::createIPAddr("20.0.0.2").unpack(), 80, 6};
|
||||
ConnKey ck6{IPAddr::createIPAddr("10::1").unpack(), 1234, IPAddr::createIPAddr("20::2").unpack(), 80, 6};
|
||||
};
|
||||
|
||||
TEST_F(PacketTest, base)
|
||||
{
|
||||
TCPPacket p(CDir::C2S);
|
||||
EXPECT_EQ(ck4, p.build(ck4)->getKey());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, move)
|
||||
{
|
||||
TCPPacket p(CDir::C2S);
|
||||
auto p2 = std::move(p);
|
||||
EXPECT_EQ(ck4, p2.build(ck4)->getKey());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, buildConn)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S).build(ck4);
|
||||
EXPECT_EQ(ck4, pkt->getKey());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, reverse)
|
||||
{
|
||||
TCPPacket p(CDir::S2C);
|
||||
ConnKey rev = ck6;
|
||||
rev.reverse();
|
||||
EXPECT_EQ(rev, p.build(ck6)->getKey());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, payloadStr)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setTCPPayload("hello")
|
||||
.build(ck4);
|
||||
EXPECT_EQ(Buffer(string("hello")), pkt->getL4Data());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, payloadVec)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setTCPPayload(vector<u_char>{'h', 'e', 'l', 'l', 'o'})
|
||||
.build(ck6);
|
||||
EXPECT_EQ(Buffer(string("hello")), pkt->getL4Data());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, TcpParams)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setTCPSeq(1234)
|
||||
.setTCPAck(5678)
|
||||
.setTCPWindow(1000)
|
||||
.setTCPFlags("SA")
|
||||
.setTCPUrgentPtr(0)
|
||||
.setTCPCksum(9999)
|
||||
.build(ck4);
|
||||
|
||||
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
|
||||
|
||||
EXPECT_EQ(constNTOHL(1234), tcp->seq);
|
||||
EXPECT_EQ(constNTOHL(5678), tcp->ack_seq);
|
||||
EXPECT_EQ(constNTOHS(1000), tcp->window);
|
||||
EXPECT_EQ(TH_SYN|TH_ACK, tcp->flags);
|
||||
EXPECT_EQ(0, tcp->urg_ptr);
|
||||
EXPECT_EQ(constNTOHS(9999), tcp->check);
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, getSeq)
|
||||
{
|
||||
auto p = TCPPacket(CDir::C2S).setTCPSeq(1234).move();
|
||||
EXPECT_EQ(1234u, p.getTCPSeq());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, l2HeaderV4)
|
||||
{
|
||||
vector<u_char> mac = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0x08, 0x00 };
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setL2Header(mac)
|
||||
.build(ck4);
|
||||
EXPECT_EQ(Buffer(vector<u_char>(mac)), pkt->getL2Header());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, l2HeaderV6)
|
||||
{
|
||||
vector<u_char> mac = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0x86, 0xdd };
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setL2Header(mac)
|
||||
.build(ck6);
|
||||
EXPECT_EQ(Buffer(vector<u_char>(mac)), pkt->getL2Header());
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsNop)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.build(ck4);
|
||||
|
||||
// 1 NOP, padded with 3 more
|
||||
EXPECT_EQ(Buffer(vector<u_char>(4, '\x01')), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsNop6)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.addTCPOption(TCPOption::NOP)
|
||||
.build(ck6);
|
||||
|
||||
// 6 NOPs, padded with 2 more
|
||||
EXPECT_EQ(Buffer(vector<u_char>(8, '\x01')), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsSACK)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::SACK_PERMITTED)
|
||||
.build(ck4);
|
||||
|
||||
// SACK_PERMITTED, len=2, 2 NOP padding
|
||||
EXPECT_EQ(Buffer(vector<u_char>{'\x04', '\x02', '\x01', '\x01'}), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsWscale)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::windowScaling(5))
|
||||
.build(ck6);
|
||||
|
||||
// Scaling, len=3, shift=5, 1 NOP padding
|
||||
EXPECT_EQ(Buffer(vector<u_char>{'\x03', '\x03', '\x05', '\x01'}), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsTstamp)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::timeStamp(0x41424344, 0x45464748))
|
||||
.build(ck4);
|
||||
|
||||
// Timestamp, len=10, value=ABCD, echo=EFGH, 2 NOP padding
|
||||
EXPECT_EQ(Buffer(string("\x08\x0a" "ABCDEFGH" "\x01\x01")), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, optionsSack)
|
||||
{
|
||||
std::vector<std::pair<uint, uint>> edges = { { 0x41424344, 0x45464748 }, { 0x30313233, 0x34353637 } };
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.addTCPOption(TCPOption::selectiveACK(edges))
|
||||
.build(ck6);
|
||||
|
||||
// SACK, len=18, pairs= ABCD, EFGH, 1234, 5678, 2 NOP padding
|
||||
EXPECT_EQ(Buffer(string("\x05\x12" "ABCDEFGH" "01234567" "\x01\x01")), getOptions(pkt.get()));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, smallHeader)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setL4HeaderSize(10) // Too small, will fail
|
||||
.build(ck4);
|
||||
EXPECT_EQ(nullptr, pkt);
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, largeDataOffset)
|
||||
{
|
||||
auto pkt = TCPPacket(CDir::C2S)
|
||||
.setL4DataOffset(6) // 6*4 is larger than packet, will fail
|
||||
.build(ck6);
|
||||
EXPECT_EQ(nullptr, pkt);
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, cksumV4)
|
||||
{
|
||||
// Get ourselves a reasonable IPv4 packet
|
||||
auto pkt = TCPPacket(CDir::C2S).build(ck4);
|
||||
auto buf = pkt->getPacket();
|
||||
auto p = buf.data();
|
||||
vector<u_char> data(p, p + buf.size());
|
||||
|
||||
// XXX: constNTOHS commetned to make it work. Endianity bug?
|
||||
auto ip = pkt->getL3Header().getTypePtr<struct ip>(0).unpack();
|
||||
EXPECT_EQ(ip->ip_sum, TCPPacket::calcIPv4Checksum(data));
|
||||
|
||||
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
|
||||
EXPECT_EQ(constNTOHS(tcp->check), TCPPacket::calcTCPv4Checksum(data));
|
||||
}
|
||||
|
||||
TEST_F(PacketTest, cksumV6)
|
||||
{
|
||||
// Get ourselves a reasonable IPv6 packet
|
||||
auto pkt = TCPPacket(CDir::C2S).build(ck6);
|
||||
auto buf = pkt->getPacket();
|
||||
auto p = buf.data();
|
||||
vector<u_char> data(p, p + buf.size());
|
||||
|
||||
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
|
||||
EXPECT_EQ(constNTOHS(tcp->check), TCPPacket::calcTCPv6Checksum(data));
|
||||
}
|
||||
67
core/cptest/cptest_ut/cptest_ut.cc
Normal file
67
core/cptest/cptest_ut/cptest_ut.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "cptest.h"
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
TEST(CPTest, PrepareToDie)
|
||||
{
|
||||
cptestPrepareToDie();
|
||||
auto die = []() {
|
||||
dbgAssert(false) << "You killed my father";
|
||||
};
|
||||
EXPECT_DEATH(die(), "You killed my father");
|
||||
}
|
||||
|
||||
TEST(Hex, parse)
|
||||
{
|
||||
auto v = cptestParseHex("0000: 01 02 03");
|
||||
EXPECT_THAT(v, ElementsAre(1, 2, 3));
|
||||
}
|
||||
|
||||
TEST(Hex, generate)
|
||||
{
|
||||
auto hex = cptestGenerateHex(vector<u_char>{'h', 'e', 'l', 'l', 'o'}, false);
|
||||
EXPECT_THAT(hex, HasSubstr("68 65 6c 6c 6f")); // hello in hex
|
||||
}
|
||||
|
||||
TEST(Hex, generateWithOffset)
|
||||
{
|
||||
auto hex = cptestGenerateHex(vector<u_char>{'h', 'e', 'l', 'l', 'o'}, true);
|
||||
EXPECT_THAT(hex, StartsWith("0000:"));
|
||||
EXPECT_THAT(hex, HasSubstr("68 65 6c 6c 6f")); // hello in hex
|
||||
}
|
||||
|
||||
TEST(File, tempEmpty)
|
||||
{
|
||||
CPTestTempfile t;
|
||||
ifstream ifs(t.fname, ifstream::in);
|
||||
ostringstream os;
|
||||
os << ifs.rdbuf();
|
||||
EXPECT_EQ("", os.str());
|
||||
}
|
||||
|
||||
TEST(File, tempNotEmpty)
|
||||
{
|
||||
vector<string> lines = {
|
||||
"hello",
|
||||
"world"
|
||||
};
|
||||
CPTestTempfile t(lines);
|
||||
ifstream ifs(t.fname, ifstream::in);
|
||||
ostringstream os;
|
||||
os << ifs.rdbuf();
|
||||
EXPECT_EQ("hello\nworld\n", os.str());
|
||||
}
|
||||
|
||||
TEST(File, pathInExeDir)
|
||||
{
|
||||
string p = cptestFnameInExeDir("try.txt");
|
||||
EXPECT_THAT(p, EndsWith("/try.txt"));
|
||||
}
|
||||
|
||||
TEST(File, pathInSrcDir)
|
||||
{
|
||||
string p = cptestFnameInSrcDir("try.txt");
|
||||
EXPECT_THAT(p, EndsWith("/core/cptest/cptest_ut/try.txt"));
|
||||
}
|
||||
3
core/cpu/CMakeLists.txt
Executable file
3
core/cpu/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
add_library(cpu cpu.cc)
|
||||
|
||||
add_subdirectory(cpu_ut)
|
||||
330
core/cpu/cpu.cc
Executable file
330
core/cpu/cpu.cc
Executable file
@@ -0,0 +1,330 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cpu.h"
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "debug.h"
|
||||
#include "log_generator.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_MONITORING);
|
||||
|
||||
static const int micro_seconds_in_second = 1000000;
|
||||
|
||||
CPUCalculator::CPUCalculator() : Component("CPUCalculator")
|
||||
{
|
||||
last_cpu_process_time = chrono::microseconds(0);
|
||||
last_cpu_general_time = chrono::microseconds(0);
|
||||
last_cpu_general_time_active = 0;
|
||||
i_time_get = nullptr;
|
||||
}
|
||||
|
||||
void CPUCalculator::init() { i_time_get = Singleton::Consume<I_TimeGet>::by<CPUCalculator>(); }
|
||||
|
||||
void CPUCalculator::fini() { i_time_get = nullptr; }
|
||||
|
||||
// LCOV_EXCL_START Reason: Compilation server dependency
|
||||
double
|
||||
CPUCalculator::GetGeneralCPUActiveTime(const cpu_data_array &cpu_data)
|
||||
{
|
||||
double current_time_active =
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::USER] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::NICE] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::SYS] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::IRQ] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::SOFTIRQ] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::STEAL] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::GUEST] +
|
||||
cpu_data[CPUCalculator::CPUGeneralDataEntryType::GUEST_NICE];
|
||||
|
||||
return (current_time_active - last_cpu_general_time_active);
|
||||
}
|
||||
|
||||
Maybe<cpu_data_array>
|
||||
CPUCalculator::getGeneralCPUData()
|
||||
{
|
||||
static const string cpu_data_file = "/proc/stat";
|
||||
ifstream fileStat(cpu_data_file);
|
||||
string line;
|
||||
|
||||
static const int max_lines_nead_to_read = 9;
|
||||
int lines_count = 0;
|
||||
while (lines_count < max_lines_nead_to_read && getline(fileStat, line))
|
||||
{
|
||||
lines_count++;
|
||||
static const string cpu_str = "cpu";
|
||||
if (line.compare(0, cpu_str.size(), cpu_str)) continue;
|
||||
istringstream iss(line);
|
||||
string ignore;
|
||||
iss >> ignore;
|
||||
cpu_data_array tmp_cpu_data;
|
||||
for (CPUGeneralDataEntryType cpu_type : NGEN::Range<CPUGeneralDataEntryType>() ) {
|
||||
string entry;
|
||||
iss >> entry;
|
||||
tmp_cpu_data[cpu_type] = atof(entry.c_str());
|
||||
}
|
||||
return tmp_cpu_data;
|
||||
}
|
||||
|
||||
return genError("Could not fill general cpu data array.");
|
||||
}
|
||||
|
||||
Maybe<double>
|
||||
CPUCalculator::getCurrentGeneralCPUUsage()
|
||||
{
|
||||
Maybe<cpu_data_array> current_cpu_data = getGeneralCPUData();
|
||||
|
||||
if (!current_cpu_data.ok()) return genError(current_cpu_data.getErr());
|
||||
|
||||
if (last_cpu_general_time == chrono::microseconds(0)) {
|
||||
last_cpu_general_time = i_time_get->getMonotonicTime();
|
||||
last_cpu_general_time_active = GetGeneralCPUActiveTime(current_cpu_data.unpack());
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto current_time = i_time_get->getMonotonicTime();
|
||||
auto elapsed_time = current_time - last_cpu_general_time;
|
||||
|
||||
double cpu_usage_active_time = GetGeneralCPUActiveTime(current_cpu_data.unpack());
|
||||
double elapsed_time_count = static_cast<double>(elapsed_time.count());
|
||||
double general_cpu_perc = cpu_usage_active_time/elapsed_time_count;
|
||||
|
||||
last_cpu_general_time = current_time;
|
||||
last_cpu_general_time_active += cpu_usage_active_time;
|
||||
|
||||
return general_cpu_perc * 100;
|
||||
}
|
||||
|
||||
double
|
||||
CPUCalculator::getCurrentProcessCPUUsage()
|
||||
{
|
||||
struct rusage usage;
|
||||
if (last_cpu_process_time == chrono::microseconds(0)) {
|
||||
last_cpu_process_time = i_time_get->getMonotonicTime();
|
||||
getrusage (RUSAGE_SELF, &usage);
|
||||
last_cpu_usage_time_in_user_mod = usage.ru_utime;
|
||||
last_cpu_usage_time_in_kernel = usage.ru_stime;
|
||||
return 0;
|
||||
}
|
||||
auto current_time = i_time_get->getMonotonicTime();
|
||||
auto elapsed_time = current_time - last_cpu_process_time;
|
||||
|
||||
getrusage(RUSAGE_SELF, &usage);
|
||||
chrono::microseconds cpu_usage_time_in_user_mod = calcTimeDiff(usage.ru_utime, last_cpu_usage_time_in_user_mod);
|
||||
chrono::microseconds cpu_usage_time_in_kernel = calcTimeDiff(usage.ru_stime, last_cpu_usage_time_in_kernel);
|
||||
|
||||
double general_cpu_time =
|
||||
static_cast<double>(cpu_usage_time_in_kernel.count() + cpu_usage_time_in_user_mod.count());
|
||||
double elapsed_time_count = static_cast<double>(elapsed_time.count());
|
||||
double general_cpu_perc = general_cpu_time/elapsed_time_count;
|
||||
|
||||
last_cpu_process_time = current_time;
|
||||
last_cpu_usage_time_in_user_mod = usage.ru_utime;
|
||||
last_cpu_usage_time_in_kernel = usage.ru_stime;
|
||||
|
||||
return general_cpu_perc * 100;
|
||||
}
|
||||
|
||||
chrono::microseconds
|
||||
CPUCalculator::calcTimeDiff(const timeval ¤t_cpu_time, const timeval &last_cpu_time) const
|
||||
{
|
||||
auto diff_in_usec = current_cpu_time.tv_usec - last_cpu_time.tv_usec;
|
||||
auto diff_in_sec = current_cpu_time.tv_sec - last_cpu_time.tv_sec;
|
||||
if (diff_in_usec < 0) {
|
||||
diff_in_usec += micro_seconds_in_second;
|
||||
diff_in_sec -= 1;
|
||||
}
|
||||
|
||||
return static_cast<chrono::microseconds>(diff_in_sec * micro_seconds_in_second + diff_in_usec);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
void
|
||||
CPUManager::loadCPUConfig()
|
||||
{
|
||||
high_watermark = getConfigurationWithDefault<uint>(85, "CPU", "high watermark");
|
||||
low_watermark = getConfigurationWithDefault<uint>(60, "CPU", "low watermark");
|
||||
watermark_period = chrono::seconds(getConfigurationWithDefault<uint>(30, "CPU", "watermark period"));
|
||||
sampling_interval = chrono::seconds(getConfigurationWithDefault<uint>(5, "CPU", "sampling interval"));
|
||||
debug_period = chrono::seconds(getConfigurationWithDefault<uint>(30, "CPU", "debug period"));
|
||||
metric_report_interval = chrono::seconds(
|
||||
getConfigurationWithDefault<uint>(600, "CPU", "metric reporting interval")
|
||||
);
|
||||
failopen_counter = watermark_period/sampling_interval;
|
||||
}
|
||||
|
||||
void
|
||||
CPUManager::init()
|
||||
{
|
||||
loadCPUConfig();
|
||||
|
||||
i_mainloop = Singleton::Consume<I_MainLoop>::by<CPUManager>();
|
||||
i_time_get = Singleton::Consume<I_TimeGet>::by<CPUManager>();
|
||||
i_cpu = Singleton::Consume<I_CPU>::by<CPUManager>();
|
||||
i_env = Singleton::Consume<I_Environment>::by<CPUManager>();
|
||||
|
||||
current_counter = 0;
|
||||
is_failopen_mode = false;
|
||||
i_env->registerValue("Failopen Status", is_failopen_mode);
|
||||
|
||||
cpu_process_metric.init(
|
||||
"CPU process usage",
|
||||
ReportIS::AudienceTeam::AGENT_CORE,
|
||||
ReportIS::IssuingEngine::AGENT_CORE,
|
||||
metric_report_interval,
|
||||
true
|
||||
);
|
||||
cpu_process_metric.registerListener();
|
||||
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto name = Singleton::Consume<I_Environment>::by<CPUManager>()->get<string>("Service Name");
|
||||
string orch_service_name = getConfigurationWithDefault<string>(
|
||||
"Orchestration",
|
||||
"orchestration",
|
||||
"Service name"
|
||||
);
|
||||
if (name.ok() && *name == orch_service_name) {
|
||||
cpu_general_metric.init(
|
||||
"CPU general usage",
|
||||
ReportIS::AudienceTeam::AGENT_CORE,
|
||||
ReportIS::IssuingEngine::AGENT_CORE,
|
||||
metric_report_interval,
|
||||
false
|
||||
);
|
||||
cpu_general_metric.registerContext<string>("Service Name", "all");
|
||||
cpu_general_metric.registerListener();
|
||||
}
|
||||
}
|
||||
|
||||
i_mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::Timer,
|
||||
[this]() { checkCPUStatus(); },
|
||||
"CPU manager status check",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
CPUManager::isFailOpenMode() const
|
||||
{
|
||||
return is_failopen_mode;
|
||||
}
|
||||
|
||||
void
|
||||
CPUManager::preload()
|
||||
{
|
||||
registerExpectedConfiguration<uint>("CPU", "high watermark");
|
||||
registerExpectedConfiguration<uint>("CPU", "low watermark");
|
||||
registerExpectedConfiguration<uint>("CPU", "watermark period");
|
||||
registerExpectedConfiguration<uint>("CPU", "sampling interval");
|
||||
registerExpectedConfiguration<uint>("CPU", "metric reporting interval");
|
||||
registerExpectedConfiguration<uint>("CPU", "debug period");
|
||||
registerExpectedConfiguration<string>("orchestration", "Service name");
|
||||
}
|
||||
|
||||
bool
|
||||
CPUManager::isCPUAboveHighWatermark(double current_cpu) const
|
||||
{
|
||||
return
|
||||
current_cpu > high_watermark &&
|
||||
current_counter >= 0 &&
|
||||
current_counter < failopen_counter;
|
||||
}
|
||||
|
||||
bool
|
||||
CPUManager::isCPUUnderHighWatermark(double current_cpu) const
|
||||
{
|
||||
return
|
||||
current_cpu < high_watermark &&
|
||||
current_counter > 0 &&
|
||||
!is_failopen_mode;
|
||||
}
|
||||
|
||||
bool
|
||||
CPUManager::isCPUUnderLowWatermark(double current_cpu) const
|
||||
{
|
||||
return current_cpu <= low_watermark && is_failopen_mode;
|
||||
}
|
||||
|
||||
void
|
||||
CPUManager::checkCPUStatus()
|
||||
{
|
||||
while (true) {
|
||||
loadCPUConfig();
|
||||
|
||||
auto is_orchestrator = Singleton::Consume<I_Environment>::by<CPUManager>()->get<bool>("Is Orchestrator");
|
||||
if (is_orchestrator.ok() && is_orchestrator.unpack()) {
|
||||
Maybe<double> current_general_cpu = i_cpu->getCurrentGeneralCPUUsage();
|
||||
if (!current_general_cpu.ok()) {
|
||||
dbgWarning(D_MONITORING) << current_general_cpu.getErr();
|
||||
} else {
|
||||
CPUEvent(current_general_cpu.unpack(), true).notify();
|
||||
}
|
||||
}
|
||||
|
||||
auto current_process_cpu = i_cpu->getCurrentProcessCPUUsage();
|
||||
dbgTrace(D_MONITORING) << "Current process CPU usage: " << current_process_cpu;
|
||||
CPUEvent(current_process_cpu, false).notify();
|
||||
|
||||
if (isCPUAboveHighWatermark(current_process_cpu)) {
|
||||
current_counter++;
|
||||
} else {
|
||||
if (isCPUUnderHighWatermark(current_process_cpu)) {
|
||||
current_counter=0;
|
||||
} else {
|
||||
if (isCPUUnderLowWatermark(current_process_cpu)) {
|
||||
current_counter--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current_counter == failopen_counter && !is_failopen_mode) {
|
||||
is_failopen_mode = true;
|
||||
i_env->registerValue("Failopen Status", is_failopen_mode);
|
||||
failopen_mode_event.setFailopenMode(is_failopen_mode);
|
||||
failopen_mode_event.notify();
|
||||
|
||||
dbgInfo(D_MONITORING) << "Failopen mode is ON, CPU usage is above "
|
||||
<< high_watermark
|
||||
<< "% for "
|
||||
<< watermark_period.count()
|
||||
<< " seconds";
|
||||
|
||||
if (debug_period == chrono::seconds::zero()) {
|
||||
dbgInfo(D_MONITORING) << "Debug period for Failopen mode is zero seconds";
|
||||
} else {
|
||||
Debug::failOpenDebugMode(debug_period);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_counter == 0 && is_failopen_mode) {
|
||||
is_failopen_mode = false;
|
||||
i_env->registerValue("Failopen Status", is_failopen_mode);
|
||||
failopen_mode_event.setFailopenMode(is_failopen_mode);
|
||||
failopen_mode_event.notify();
|
||||
|
||||
dbgInfo(D_MONITORING) << "Failopen mode is OFF, CPU usage is below "
|
||||
<< low_watermark
|
||||
<< "% for "
|
||||
<< watermark_period.count()
|
||||
<< " seconds";
|
||||
}
|
||||
|
||||
i_mainloop->yield(sampling_interval);
|
||||
}
|
||||
}
|
||||
5
core/cpu/cpu_ut/CMakeLists.txt
Executable file
5
core/cpu/cpu_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
cpu_ut
|
||||
"cpu_ut.cc"
|
||||
"cpu;event_is;metric;-lboost_regex"
|
||||
)
|
||||
643
core/cpu/cpu_ut/cpu_ut.cc
Executable file
643
core/cpu/cpu_ut/cpu_ut.cc
Executable file
@@ -0,0 +1,643 @@
|
||||
#include "cpu.h"
|
||||
|
||||
#include "mock/mock_cpu.h"
|
||||
#include "cptest.h"
|
||||
#include "cptest.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "environment.h"
|
||||
#include "event.h"
|
||||
#include "listener.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace chrono;
|
||||
|
||||
USE_DEBUG_FLAG(D_FW);
|
||||
USE_DEBUG_FLAG(D_CONFIG);
|
||||
|
||||
string line = "";
|
||||
|
||||
void doFWError() { dbgError(D_FW) << "FW error message"; line = to_string(__LINE__); }
|
||||
void doFWWarning() { dbgWarning(D_FW) << "FW warning message"; line = to_string(__LINE__); }
|
||||
void doFWInfo() { dbgInfo(D_FW) << "FW info message"; line = to_string(__LINE__); }
|
||||
void doFWDebug() { dbgDebug(D_FW) << "FW debug message"; line = to_string(__LINE__); }
|
||||
void doFWTrace() { dbgTrace(D_FW) << "FW trace message"; line = to_string(__LINE__); }
|
||||
|
||||
class TestEnd {};
|
||||
|
||||
static ostream & operator<<(ostream &os, const Context::Error &) { return os; }
|
||||
|
||||
class CPUTest : public Test
|
||||
{
|
||||
public:
|
||||
CPUTest()
|
||||
{
|
||||
env.preload();
|
||||
env.init();
|
||||
i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
i_env->registerValue<bool>("Is Orchestrator", true);
|
||||
EXPECT_CALL(mock_ml, getCurrentRoutineId()).WillRepeatedly(Return(5));
|
||||
}
|
||||
|
||||
~CPUTest() { Debug::setNewDefaultStdout(&cout); }
|
||||
|
||||
StrictMock<MockMainLoop> mock_ml;
|
||||
StrictMock<MockTimeGet> mock_time;
|
||||
I_Environment *i_env;
|
||||
|
||||
private:
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
};
|
||||
|
||||
class FailopenModeListener : public Listener<FailopenModeEvent>
|
||||
{
|
||||
public:
|
||||
void
|
||||
upon(const FailopenModeEvent &event) override
|
||||
{
|
||||
current_failopen_status = event.getFailopenMode();
|
||||
}
|
||||
|
||||
bool
|
||||
isFailopenMode() const
|
||||
{
|
||||
return current_failopen_status;
|
||||
}
|
||||
|
||||
private:
|
||||
bool current_failopen_status = false;
|
||||
};
|
||||
|
||||
TEST_F(CPUTest, basicTest)
|
||||
{
|
||||
seconds time = seconds(0);
|
||||
|
||||
Debug::init();
|
||||
stringstream debug_output;
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_FW, Debug::DebugLevel::ERROR);
|
||||
Debug::setUnitTestFlag(D_CONFIG, Debug::DebugLevel::ERROR);
|
||||
|
||||
FailopenModeListener failopen_mode_listener;
|
||||
failopen_mode_listener.registerListener();
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillRepeatedly(Return(microseconds(time+=seconds(1))));
|
||||
EXPECT_CALL(mock_time, getWalltime()).WillRepeatedly(Return(microseconds(1)));
|
||||
EXPECT_CALL(mock_time, getWalltimeStr()).WillRepeatedly(Return(string("2016-11-13T17:31:24.087")));
|
||||
|
||||
I_MainLoop::Routine cpu_routine = nullptr;
|
||||
I_MainLoop::Routine debug_routine = nullptr;
|
||||
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::Timer, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&cpu_routine), Return(0)));
|
||||
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&debug_routine), Return(0)));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_ml,
|
||||
addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
chrono::microseconds(600000000),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
)
|
||||
).WillRepeatedly(Return(1));
|
||||
|
||||
StrictMock<MockCPU> mock_cpu;
|
||||
CPUManager cpu;
|
||||
cpu.init();
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message"));
|
||||
debug_output.str("");
|
||||
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
|
||||
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>())).WillRepeatedly(Invoke(
|
||||
[&] (chrono::microseconds duration) {
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(5)).count());
|
||||
static int count = 0;
|
||||
count++;
|
||||
if (count <= 5) {
|
||||
//Getting 90% CPU for 30 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count > 5 && count <= 11) {
|
||||
//Getting 50% CPU for 30 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_TRUE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(true));
|
||||
EXPECT_TRUE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count == 12) {
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
throw TestEnd();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
try {
|
||||
cpu_routine();
|
||||
} catch(const TestEnd &T) {
|
||||
//During Failopen mode debugs will be ON
|
||||
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>()))
|
||||
.WillOnce(
|
||||
Invoke(
|
||||
[&] (chrono::microseconds duration)
|
||||
{
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(30)).count());
|
||||
debug_output.str("");
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("@@@] FW debug message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(">>>] FW trace message\n"));
|
||||
debug_output.str("");
|
||||
}
|
||||
)
|
||||
);
|
||||
debug_routine();
|
||||
}
|
||||
|
||||
//Exiting Failopen mode - debugs will be back to pervious state
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
failopen_mode_listener.unregisterListener();
|
||||
Debug::fini();
|
||||
}
|
||||
|
||||
TEST_F(CPUTest, noDebugTest)
|
||||
{
|
||||
seconds time = seconds(0);
|
||||
|
||||
Debug::init();
|
||||
stringstream debug_output;
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_FW, Debug::DebugLevel::INFO);
|
||||
|
||||
FailopenModeListener failopen_mode_listener;
|
||||
failopen_mode_listener.registerListener();
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillRepeatedly(Return(microseconds(time+=seconds(1))));
|
||||
EXPECT_CALL(mock_time, getWalltime()).WillRepeatedly(Return(microseconds(1)));
|
||||
EXPECT_CALL(mock_time, getWalltimeStr()).WillRepeatedly(Return(string("2016-11-13T17:31:24.087")));
|
||||
|
||||
I_MainLoop::Routine cpu_routine = nullptr;
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::Timer, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&cpu_routine), Return(0)));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_ml,
|
||||
addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
chrono::microseconds(600000000),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
)
|
||||
).WillRepeatedly(Return(1));
|
||||
|
||||
StrictMock<MockCPU> mock_cpu;
|
||||
CPUManager cpu;
|
||||
cpu.preload();
|
||||
setConfiguration<uint>(0, string("CPU"), string("debug period"));
|
||||
cpu.init();
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
|
||||
debug_output.str("");
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
|
||||
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>())).WillRepeatedly(Invoke(
|
||||
[&] (chrono::microseconds duration) {
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(5)).count());
|
||||
static int count = 0;
|
||||
count++;
|
||||
if (count <= 5) {
|
||||
//Getting 90% CPU for 30 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count > 5 && count <= 11) {
|
||||
//Getting 50% CPU for 30 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_TRUE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(true));
|
||||
EXPECT_TRUE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count == 12) {
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
throw TestEnd();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
try {
|
||||
cpu_routine();
|
||||
} catch(const TestEnd &T) {}
|
||||
|
||||
EXPECT_THAT(
|
||||
debug_output.str(),
|
||||
HasSubstr("Failopen mode is ON, CPU usage is above 85% for 30 seconds")
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
debug_output.str(),
|
||||
HasSubstr("Debug period for Failopen mode is zero seconds")
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
debug_output.str(),
|
||||
HasSubstr("Failopen mode is OFF, CPU usage is below 60% for 30 seconds")
|
||||
);
|
||||
|
||||
debug_output.str("");
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
failopen_mode_listener.unregisterListener();
|
||||
Debug::fini();
|
||||
}
|
||||
|
||||
TEST_F(CPUTest, CPUCalculatorConstructor)
|
||||
{
|
||||
seconds time = seconds(0);
|
||||
Debug::init();
|
||||
stringstream debug_output;
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_FW, Debug::DebugLevel::INFO);
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillRepeatedly(Return(microseconds(time+=seconds(1))));
|
||||
EXPECT_CALL(mock_time, getWalltime()).WillRepeatedly(Return(microseconds(1)));
|
||||
EXPECT_CALL(mock_time, getWalltimeStr()).WillRepeatedly(Return(string("2016-11-13T17:31:24.087")));
|
||||
I_MainLoop::Routine cpu_routine = nullptr;
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::Timer, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&cpu_routine), Return(0)));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_ml,
|
||||
addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
chrono::microseconds(600000000),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
)
|
||||
).WillRepeatedly(Return(1));
|
||||
|
||||
CPUCalculator cpu_calc;
|
||||
CPUManager cpu;
|
||||
cpu.preload();
|
||||
cpu_calc.init();
|
||||
cpu.init();
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
|
||||
debug_output.str("");
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
cpu_calc.fini();
|
||||
}
|
||||
|
||||
TEST_F(CPUTest, TwoFailopenDebugTest)
|
||||
{
|
||||
seconds time = seconds(0);
|
||||
|
||||
Debug::init();
|
||||
stringstream debug_output;
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_FW, Debug::DebugLevel::ERROR);
|
||||
|
||||
FailopenModeListener failopen_mode_listener;
|
||||
failopen_mode_listener.registerListener();
|
||||
|
||||
EXPECT_CALL(mock_time, getMonotonicTime()).WillRepeatedly(Return(microseconds(time+=seconds(1))));
|
||||
EXPECT_CALL(mock_time, getWalltime()).WillRepeatedly(Return(microseconds(1)));
|
||||
EXPECT_CALL(mock_time, getWalltimeStr()).WillRepeatedly(Return(string("2016-11-13T17:31:24.087")));
|
||||
|
||||
I_MainLoop::Routine cpu_routine = nullptr;
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::Timer, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&cpu_routine), Return(0)));
|
||||
|
||||
I_MainLoop::Routine first_debug_routine = nullptr;
|
||||
I_MainLoop::Routine second_debug_routine = nullptr;
|
||||
EXPECT_CALL(mock_ml, addOneTimeRoutine(I_MainLoop::RoutineType::System, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<1>(&first_debug_routine), Return(0)))
|
||||
.WillOnce(DoAll(SaveArg<1>(&second_debug_routine), Return(0)));
|
||||
|
||||
EXPECT_CALL(
|
||||
mock_ml,
|
||||
addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
chrono::microseconds(600000000),
|
||||
_,
|
||||
_,
|
||||
_
|
||||
)
|
||||
).WillRepeatedly(Return(1));
|
||||
|
||||
StrictMock<MockCPU> mock_cpu;
|
||||
CPUManager cpu;
|
||||
setConfiguration<uint>(90, string("CPU"), string("debug period"));
|
||||
setConfiguration<uint>(25, "CPU", "watermark period");
|
||||
cpu.init();
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
|
||||
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>())).WillRepeatedly(Invoke(
|
||||
[&] (chrono::microseconds duration) {
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(5)).count());
|
||||
static int count = 0;
|
||||
count++;
|
||||
if (count <= 4) {
|
||||
//Getting 90% CPU for 5 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count > 4 && count <= 9) {
|
||||
//Getting 50% CPU for 5 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_TRUE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(true));
|
||||
EXPECT_TRUE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count > 9 && count <= 14) {
|
||||
//Getting 90% CPU for 5 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(90));
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count > 14 && count <= 19) {
|
||||
//Getting 50% CPU for 5 seconds
|
||||
EXPECT_CALL(mock_cpu, getCurrentProcessCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_CALL(mock_cpu, getCurrentGeneralCPUUsage()).WillOnce(Return(50));
|
||||
EXPECT_TRUE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(true));
|
||||
EXPECT_TRUE(failopen_mode_listener.isFailopenMode());
|
||||
}
|
||||
if (count == 20) {
|
||||
EXPECT_FALSE(cpu.isFailOpenMode());
|
||||
EXPECT_THAT(i_env->get<bool>("Failopen Status"), IsValue(false));
|
||||
EXPECT_FALSE(failopen_mode_listener.isFailopenMode());
|
||||
throw TestEnd();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
try {
|
||||
cpu_routine();
|
||||
} catch(const TestEnd &T) {
|
||||
//During Failopen mode debugs will be ON
|
||||
EXPECT_CALL(mock_ml, yield(A<chrono::microseconds>()))
|
||||
.WillOnce(
|
||||
Invoke(
|
||||
[&] (chrono::microseconds duration)
|
||||
{
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(90)).count());
|
||||
debug_output.str("");
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("@@@] FW debug message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(">>>] FW trace message\n"));
|
||||
debug_output.str("");
|
||||
}
|
||||
)
|
||||
)
|
||||
.WillOnce(
|
||||
Invoke(
|
||||
[&] (chrono::microseconds duration)
|
||||
{
|
||||
EXPECT_EQ(duration.count(), chrono::microseconds(chrono::seconds(90)).count());
|
||||
debug_output.str("");
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("@@@] FW debug message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(">>>] FW trace message\n"));
|
||||
debug_output.str("");
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
first_debug_routine();
|
||||
|
||||
//Exiting first Failopen mode - debugs are still enabled as only second failopen end will turn them off
|
||||
debug_output.str("");
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("---] FW info message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("###] FW warning message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("@@@] FW debug message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(">>>] FW trace message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
second_debug_routine();
|
||||
}
|
||||
|
||||
// Back to previous debug state
|
||||
doFWInfo();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWWarning();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWDebug();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWTrace();
|
||||
EXPECT_THAT(debug_output.str(), "");
|
||||
debug_output.str("");
|
||||
|
||||
doFWError();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("!!!] FW error message\n"));
|
||||
debug_output.str("");
|
||||
|
||||
failopen_mode_listener.unregisterListener();
|
||||
Debug::fini();
|
||||
}
|
||||
3
core/debug_is/CMakeLists.txt
Normal file
3
core/debug_is/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(debug_is debug.cc debug_streams.cc)
|
||||
|
||||
add_subdirectory(debug_is_ut)
|
||||
746
core/debug_is/debug.cc
Executable file
746
core/debug_is/debug.cc
Executable file
@@ -0,0 +1,746 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "debug_ex.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "enum_array.h"
|
||||
#include "i_time_get.h"
|
||||
#include "i_mainloop.h"
|
||||
#include "i_environment.h"
|
||||
#include "config.h"
|
||||
#include "i_instance_awareness.h"
|
||||
#include "i_signal_handler.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using FlagsArray = EnumArray<Debug::DebugFlags, Debug::DebugLevel>;
|
||||
|
||||
static constexpr Debug::DebugLevel default_level = Debug::DebugLevel::INFO;
|
||||
|
||||
#define DEFINE_FLAG(flag_name, parent_name) \
|
||||
extern const Debug::DebugFlags flag_name = Debug::DebugFlags::flag_name;
|
||||
#include "debug_flags.h"
|
||||
#undef DEFINE_FLAG
|
||||
|
||||
static multimap<Debug::DebugFlags, Debug::DebugFlags> flags_hierarchy = {
|
||||
|
||||
#define DEFINE_FLAG(flag_name, parent_name) \
|
||||
{ Debug::DebugFlags::parent_name, Debug::DebugFlags::flag_name },
|
||||
#include "debug_flags.h"
|
||||
#undef DEFINE_FLAG
|
||||
|
||||
};
|
||||
|
||||
static map<string, shared_ptr<Debug::DebugStream>> active_streams = {
|
||||
{ "STDOUT", make_shared<Debug::DebugStream>(&cout) }
|
||||
};
|
||||
|
||||
pair<Debug::DebugFlags, string>
|
||||
convertFlagToSettingString(const string &flag_name, const Debug::DebugFlags &flag)
|
||||
{
|
||||
static const string setting_name_prefix = "agent.debug.flag.";
|
||||
|
||||
string debug_setting_name = setting_name_prefix;
|
||||
string token;
|
||||
istringstream tokenStream(flag_name);
|
||||
uint iter_num = 0;
|
||||
while (getline(tokenStream, token, '_'))
|
||||
{
|
||||
if (iter_num == 0 && token == "D") {
|
||||
iter_num++;
|
||||
continue;
|
||||
}
|
||||
|
||||
transform(token.begin(), token.end(), token.begin(), [](unsigned char letter){ return tolower(letter); });
|
||||
if (iter_num > 1) token.front() = toupper(token.front());
|
||||
iter_num++;
|
||||
debug_setting_name += token;
|
||||
}
|
||||
return make_pair(flag, debug_setting_name);
|
||||
}
|
||||
|
||||
static map<Debug::DebugFlags, string> flags_to_setting_name = {
|
||||
convertFlagToSettingString("D_ALL", Debug::DebugFlags::D_ALL),
|
||||
#define DEFINE_FLAG(flag_name, parent_name) convertFlagToSettingString(#flag_name, Debug::DebugFlags::flag_name),
|
||||
#include "debug_flags.h"
|
||||
#undef DEFINE_FLAG
|
||||
};
|
||||
|
||||
static map<string, shared_ptr<Debug::DebugStream>> preparing_streams;
|
||||
|
||||
static FlagsArray global_flags_levels(FlagsArray::Fill(), default_level);
|
||||
static FlagsArray flags_levels_override(FlagsArray::Fill(), Debug::DebugLevel::NOISE);
|
||||
static FlagsArray preparing_global_flags;
|
||||
|
||||
class DebugStreamConfiguration
|
||||
{
|
||||
public:
|
||||
DebugStreamConfiguration(const string &_stream_name)
|
||||
:
|
||||
stream_name(_stream_name)
|
||||
{
|
||||
if(stream_name == "FOG") {
|
||||
flag_values.fill(Debug::DebugLevel::ERROR);
|
||||
}
|
||||
else {
|
||||
flag_values.fill(default_level);
|
||||
}
|
||||
}
|
||||
|
||||
DebugStreamConfiguration() : DebugStreamConfiguration("STDOUT") {}
|
||||
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("Output", stream_name));
|
||||
if (stream_name != "FOG" && stream_name != "STDOUT" && stream_name.front() != '/') {
|
||||
stream_name = getLogFilesPathConfig() + "/" + stream_name;
|
||||
}
|
||||
#define DEFINE_FLAG(flag_name, parent_name) \
|
||||
try { \
|
||||
string level; \
|
||||
ar(cereal::make_nvp(#flag_name, level)); \
|
||||
assignValueToFlagRecursively(flag_values, Debug::DebugFlags::flag_name, turnToLevel(level)); \
|
||||
} catch (cereal::Exception &) { \
|
||||
ar.setNextName(nullptr); \
|
||||
}
|
||||
DEFINE_FLAG(D_ALL, D_ALL)
|
||||
#include "debug_flags.h"
|
||||
#undef DEFINE_FLAG
|
||||
|
||||
for (auto flag : makeRange<Debug::DebugFlags>()) {
|
||||
if (flag_values[flag] < preparing_global_flags[flag]) preparing_global_flags[flag] = flag_values[flag];
|
||||
}
|
||||
|
||||
insertConfigurationToPendingMap();
|
||||
}
|
||||
|
||||
static void
|
||||
assignValueToFlagRecursively(FlagsArray &flag_levels, Debug::DebugFlags flag, Debug::DebugLevel level)
|
||||
{
|
||||
flag_levels[flag] = level;
|
||||
auto sub_flags_range = flags_hierarchy.equal_range(flag);
|
||||
for (auto flag_iterator = sub_flags_range.first; flag_iterator != sub_flags_range.second; flag_iterator++) {
|
||||
assignValueToFlagRecursively(flag_levels, flag_iterator->second, level);
|
||||
}
|
||||
}
|
||||
|
||||
FlagsArray flag_values;
|
||||
string stream_name;
|
||||
|
||||
private:
|
||||
Debug::DebugLevel
|
||||
turnToLevel(const string &level)
|
||||
{
|
||||
if (level == "Error") return Debug::DebugLevel::ERROR;
|
||||
if (level == "Warning") return Debug::DebugLevel::WARNING;
|
||||
if (level == "Info") return Debug::DebugLevel::INFO;
|
||||
if (level == "Debug") return Debug::DebugLevel::DEBUG;
|
||||
if (level == "Trace") return Debug::DebugLevel::TRACE;
|
||||
|
||||
throw Config::ConfigException("Illegal debug flag level");
|
||||
return Debug::DebugLevel::NOISE;
|
||||
}
|
||||
|
||||
void
|
||||
insertConfigurationToPendingMap()
|
||||
{
|
||||
if (stream_name.empty()) return;
|
||||
|
||||
if (preparing_streams.count(stream_name) > 0) return;
|
||||
|
||||
if (active_streams.count(stream_name) > 0) {
|
||||
preparing_streams[stream_name] = active_streams[stream_name];
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream_name == "STDOUT") {
|
||||
preparing_streams[stream_name] = make_shared<Debug::DebugStream>(&cout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream_name == "FOG") {
|
||||
preparing_streams[stream_name] = make_shared<DebugFogStream>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidFileStreamName()) throw Config::ConfigException("Illegal debug stream name: " + stream_name);
|
||||
|
||||
auto inst_aware =
|
||||
Singleton::exists<I_InstanceAwareness>() ? Singleton::Consume<I_InstanceAwareness>::by<Debug>() : nullptr;
|
||||
preparing_streams[stream_name] = make_shared<DebugFileStream>(
|
||||
stream_name + (inst_aware ? inst_aware->getUniqueID("") : "")
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
isValidFileStreamName()
|
||||
{
|
||||
string debug_file_prefix = Debug::findDebugFilePrefix(stream_name);
|
||||
if (debug_file_prefix == "") return false;
|
||||
|
||||
auto file_name_begins = stream_name.begin() + debug_file_prefix.size();
|
||||
int num_forbidden_chars = count_if(
|
||||
file_name_begins,
|
||||
stream_name.end(),
|
||||
[] (unsigned char c) { return !isalnum(c) && c != '/' && c != '_' && c != '-' && c != '.'; }
|
||||
);
|
||||
if (num_forbidden_chars > 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class DebugConfiguration
|
||||
{
|
||||
public:
|
||||
DebugConfiguration()
|
||||
{
|
||||
streams_in_context.push_back(DebugStreamConfiguration());
|
||||
streams_in_context.push_back(DebugStreamConfiguration("FOG"));
|
||||
}
|
||||
|
||||
DebugConfiguration(const string &stream) : DebugConfiguration()
|
||||
{
|
||||
streams_in_context.push_back(DebugStreamConfiguration(stream));
|
||||
streams_in_context.push_back(DebugStreamConfiguration("FOG"));
|
||||
}
|
||||
|
||||
vector<DebugStreamConfiguration> streams_in_context;
|
||||
|
||||
void
|
||||
load(cereal::JSONInputArchive &ar)
|
||||
{
|
||||
ar(cereal::make_nvp("Streams", streams_in_context));
|
||||
}
|
||||
};
|
||||
|
||||
static DebugConfiguration default_config;
|
||||
|
||||
// LCOV_EXCL_START - function is covered in unit-test, but not detected bt gcov
|
||||
Debug::Debug(
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line)
|
||||
{
|
||||
if (Singleton::exists<Config::I_Config>()) {
|
||||
do_assert = getConfigurationWithDefault<bool>(true, "Debug I/S", "Abort on assertion");
|
||||
} else {
|
||||
do_assert = true;
|
||||
}
|
||||
|
||||
auto current_configuration =
|
||||
Singleton::exists<Config::I_Config>() ? getConfigurationWithDefault(default_config, "Debug") : default_config;
|
||||
|
||||
for (auto &stream : current_configuration.streams_in_context) {
|
||||
addActiveStream(stream.stream_name);
|
||||
}
|
||||
|
||||
for (const string &stream_name : streams_from_mgmt) {
|
||||
addActiveStream(stream_name);
|
||||
}
|
||||
startStreams(DebugLevel::ASSERTION, file_name, func_name, line);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
#define evalWithOverride(orig_cond, flag, level) ( \
|
||||
(debug_override_exist && flags_levels_override[flag] != Debug::DebugLevel::NOISE) ? \
|
||||
flags_levels_override[flag] <= level : \
|
||||
(orig_cond) \
|
||||
)
|
||||
|
||||
bool
|
||||
Debug::shouldApplyFailOpenOnStream(const string &name) const
|
||||
{
|
||||
return name != "FOG" && is_fail_open_mode;
|
||||
}
|
||||
|
||||
Debug::Debug(
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line,
|
||||
const DebugLevel &level,
|
||||
const DebugFlags &flag1)
|
||||
:
|
||||
do_assert(false)
|
||||
{
|
||||
auto current_configuration =
|
||||
Singleton::exists<Config::I_Config>() ? getConfigurationWithDefault(default_config, "Debug") : default_config;
|
||||
for (auto &stream : current_configuration.streams_in_context) {
|
||||
if (shouldApplyFailOpenOnStream(stream.stream_name) ||
|
||||
evalWithOverride((stream.flag_values[flag1] <= level), flag1, level)
|
||||
) {
|
||||
addActiveStream(stream.stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const string &stream_name : streams_from_mgmt) {
|
||||
if (shouldApplyFailOpenOnStream(stream_name) || evalWithOverride(false, flag1, level)) {
|
||||
addActiveStream(stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
startStreams(level, file_name, func_name, line);
|
||||
}
|
||||
|
||||
Debug::Debug(
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line,
|
||||
const DebugLevel &level,
|
||||
const DebugFlags &flag1,
|
||||
const DebugFlags &flag2)
|
||||
:
|
||||
do_assert(false)
|
||||
{
|
||||
auto current_configuration =
|
||||
Singleton::exists<Config::I_Config>() ? getConfigurationWithDefault(default_config, "Debug") : default_config;
|
||||
|
||||
for (auto &stream : current_configuration.streams_in_context) {
|
||||
if (shouldApplyFailOpenOnStream(stream.stream_name) ||
|
||||
evalWithOverride((stream.flag_values[flag1] <= level), flag1, level) ||
|
||||
evalWithOverride((stream.flag_values[flag2] <= level), flag2, level)
|
||||
) {
|
||||
addActiveStream(stream.stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const string &stream_name : streams_from_mgmt) {
|
||||
if (shouldApplyFailOpenOnStream(stream_name) ||
|
||||
evalWithOverride(false, flag1, level) ||
|
||||
evalWithOverride(false, flag2, level)
|
||||
) {
|
||||
addActiveStream(stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
startStreams(level, file_name, func_name, line);
|
||||
}
|
||||
|
||||
Debug::Debug(
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line,
|
||||
const DebugLevel &level,
|
||||
const DebugFlags &flag1,
|
||||
const DebugFlags &flag2,
|
||||
const DebugFlags &flag3)
|
||||
:
|
||||
do_assert(false)
|
||||
{
|
||||
auto current_configuration =
|
||||
Singleton::exists<Config::I_Config>() ? getConfigurationWithDefault(default_config, "Debug") : default_config;
|
||||
|
||||
for (auto &stream : current_configuration.streams_in_context) {
|
||||
if (shouldApplyFailOpenOnStream(stream.stream_name) ||
|
||||
evalWithOverride((stream.flag_values[flag1] <= level), flag1, level) ||
|
||||
evalWithOverride((stream.flag_values[flag2] <= level), flag2, level) ||
|
||||
evalWithOverride((stream.flag_values[flag3] <= level), flag3, level)
|
||||
) {
|
||||
addActiveStream(stream.stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const string &stream_name : streams_from_mgmt) {
|
||||
if (shouldApplyFailOpenOnStream(stream_name) ||
|
||||
evalWithOverride(false, flag1, level) ||
|
||||
evalWithOverride(false, flag2, level) ||
|
||||
evalWithOverride(false, flag3, level)
|
||||
) {
|
||||
addActiveStream(stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
startStreams(level, file_name, func_name, line);
|
||||
}
|
||||
|
||||
Debug::Debug(
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line,
|
||||
const DebugLevel &level,
|
||||
const DebugFlags &flag1,
|
||||
const DebugFlags &flag2,
|
||||
const DebugFlags &flag3,
|
||||
const DebugFlags &flag4)
|
||||
:
|
||||
do_assert(false)
|
||||
{
|
||||
auto current_configuration =
|
||||
Singleton::exists<Config::I_Config>() ? getConfigurationWithDefault(default_config, "Debug") : default_config;
|
||||
|
||||
for (auto &stream : current_configuration.streams_in_context) {
|
||||
if (shouldApplyFailOpenOnStream(stream.stream_name) ||
|
||||
evalWithOverride((stream.flag_values[flag1] <= level), flag1, level) ||
|
||||
evalWithOverride((stream.flag_values[flag2] <= level), flag2, level) ||
|
||||
evalWithOverride((stream.flag_values[flag3] <= level), flag3, level) ||
|
||||
evalWithOverride((stream.flag_values[flag4] <= level), flag4, level)
|
||||
) {
|
||||
addActiveStream(stream.stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const string &stream_name : streams_from_mgmt) {
|
||||
if (shouldApplyFailOpenOnStream(stream_name) ||
|
||||
evalWithOverride(false, flag1, level) ||
|
||||
evalWithOverride(false, flag2, level) ||
|
||||
evalWithOverride(false, flag3, level) ||
|
||||
evalWithOverride(false, flag4, level)
|
||||
) {
|
||||
addActiveStream(stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
startStreams(level, file_name, func_name, line);
|
||||
}
|
||||
|
||||
Debug::~Debug()
|
||||
{
|
||||
if (do_assert) {
|
||||
stream << "\nPanic!";
|
||||
printBacktraceBeforeAbort();
|
||||
}
|
||||
|
||||
for (auto &added_stream : current_active_streams) {
|
||||
added_stream->finishMessage();
|
||||
}
|
||||
|
||||
if (do_assert) abort();
|
||||
|
||||
is_debug_running = false;
|
||||
}
|
||||
|
||||
void
|
||||
Debug::preload()
|
||||
{
|
||||
registerExpectedConfiguration<DebugConfiguration>("Debug");
|
||||
registerExpectedConfiguration<string>("Debug I/S", "Fog Debug URI");
|
||||
registerExpectedConfiguration<bool>("Debug I/S", "Enable bulk of debugs");
|
||||
registerExpectedConfiguration<uint>("Debug I/S", "Debug bulk size");
|
||||
registerExpectedConfiguration<uint>("Debug I/S", "Debug bulk sending interval in msec");
|
||||
registerExpectedConfiguration<uint>("Debug I/S", "Threshold debug bulk size");
|
||||
registerExpectedConfiguration<bool>("Debug I/S", "Abort on assertion");
|
||||
|
||||
registerConfigPrepareCb(Debug::prepareConfig);
|
||||
registerConfigLoadCb(Debug::commitConfig);
|
||||
registerConfigAbortCb(Debug::abortConfig);
|
||||
|
||||
active_streams["STDOUT"] = make_shared<Debug::DebugStream>(&cout);
|
||||
active_streams["FOG"] = make_shared<DebugFogStream>();
|
||||
}
|
||||
|
||||
void
|
||||
Debug::init()
|
||||
{
|
||||
time = Singleton::Consume<I_TimeGet>::by<Debug>();
|
||||
mainloop = Singleton::Consume<I_MainLoop>::by<Debug>();
|
||||
env = Singleton::Consume<I_Environment>::by<Debug>();
|
||||
|
||||
auto executable = env->get<string>("Executable Name");
|
||||
|
||||
if (executable.ok() && *executable != "") {
|
||||
string default_debug_output_file_path = *executable;
|
||||
auto file_path_end = default_debug_output_file_path.find_last_of("/");
|
||||
if (file_path_end != string::npos) {
|
||||
default_debug_file_stream_path = default_debug_output_file_path.substr(file_path_end + 1);
|
||||
}
|
||||
auto file_sufix_start = default_debug_file_stream_path.find_first_of(".");
|
||||
if (file_sufix_start != string::npos) {
|
||||
default_debug_file_stream_path = default_debug_file_stream_path.substr(0, file_sufix_start);
|
||||
}
|
||||
|
||||
string log_files_prefix = getLogFilesPathConfig();
|
||||
default_debug_file_stream_path = log_files_prefix + "/nano_agent/" + default_debug_file_stream_path + ".dbg";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Debug::fini()
|
||||
{
|
||||
time = nullptr;
|
||||
mainloop = nullptr;
|
||||
env = nullptr;
|
||||
active_streams.clear();
|
||||
}
|
||||
|
||||
void
|
||||
Debug::prepareConfig()
|
||||
{
|
||||
preparing_streams.clear();
|
||||
preparing_global_flags.fill(default_level);
|
||||
}
|
||||
|
||||
void
|
||||
Debug::abortConfig()
|
||||
{
|
||||
preparing_streams.clear();
|
||||
}
|
||||
|
||||
Debug::DebugLevel
|
||||
getLevelFromSettingString(const string &level)
|
||||
{
|
||||
if (level == "error") return Debug::DebugLevel::ERROR;
|
||||
if (level == "warning") return Debug::DebugLevel::WARNING;
|
||||
if (level == "info") return Debug::DebugLevel::INFO;
|
||||
if (level == "debug") return Debug::DebugLevel::DEBUG;
|
||||
if (level == "trace") return Debug::DebugLevel::TRACE;
|
||||
|
||||
return Debug::DebugLevel::NOISE;
|
||||
}
|
||||
|
||||
void
|
||||
Debug::applyOverrides()
|
||||
{
|
||||
streams_from_mgmt.clear();
|
||||
auto fog_stream_setting_state = getProfileAgentSetting<bool>("agent.debug.stream.fog");
|
||||
if (fog_stream_setting_state.ok()) {
|
||||
if (*fog_stream_setting_state == false) {
|
||||
active_streams.erase("FOG");
|
||||
} else if (active_streams.find("FOG") == active_streams.end()) {
|
||||
active_streams["FOG"] = make_shared<DebugFogStream>();
|
||||
streams_from_mgmt.push_back("FOG");
|
||||
}
|
||||
}
|
||||
|
||||
auto local_stream_setting_state = getProfileAgentSetting<bool>("agent.debug.stream.file");
|
||||
if (local_stream_setting_state.ok()) {
|
||||
if (*local_stream_setting_state == false) {
|
||||
vector<string> active_stream_keys;
|
||||
for (auto stream : active_streams) {
|
||||
if (stream.first != "FOG") active_stream_keys.push_back(stream.first);
|
||||
}
|
||||
for (auto stream : active_stream_keys) {
|
||||
active_streams.erase(stream);
|
||||
}
|
||||
} else {
|
||||
auto should_add_file_stream = true;
|
||||
for (const pair<string, shared_ptr<Debug::DebugStream>> &elem : active_streams) {
|
||||
if (elem.first != "STDOUT" && elem.first != "FOG") should_add_file_stream = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (should_add_file_stream) {
|
||||
if (default_debug_file_stream_path != "") {
|
||||
streams_from_mgmt.push_back(default_debug_file_stream_path);
|
||||
|
||||
auto inst_aware = Singleton::exists<I_InstanceAwareness>() ?
|
||||
Singleton::Consume<I_InstanceAwareness>::by<Debug>() :
|
||||
nullptr;
|
||||
|
||||
active_streams[default_debug_file_stream_path] = make_shared<DebugFileStream>(
|
||||
default_debug_file_stream_path + string(inst_aware ? inst_aware->getUniqueID("") : "")
|
||||
);
|
||||
} else {
|
||||
active_streams["STDOUT"] = make_shared<DebugStream>(&cout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_override_exist = false;
|
||||
flags_levels_override.fill(Debug::DebugLevel::NOISE);
|
||||
for (auto setting_flag : flags_to_setting_name) {
|
||||
auto override = getProfileAgentSetting<string>(setting_flag.second);
|
||||
if (!override.ok()) continue;
|
||||
|
||||
Debug::DebugLevel level = getLevelFromSettingString(override.unpack());
|
||||
if (level == Debug::DebugLevel::NOISE) continue;
|
||||
|
||||
debug_override_exist = true;
|
||||
DebugStreamConfiguration::assignValueToFlagRecursively(flags_levels_override, setting_flag.first, level);
|
||||
}
|
||||
|
||||
if (getProfileAgentSettingWithDefault<bool>(false, "agent.debug.stream.kernel")) {
|
||||
debug_override_exist = true;
|
||||
DebugStreamConfiguration::assignValueToFlagRecursively(
|
||||
flags_levels_override,
|
||||
Debug::DebugFlags::D_MESSAGE_READER,
|
||||
Debug::DebugLevel::TRACE
|
||||
);
|
||||
}
|
||||
|
||||
if (!debug_override_exist) return;
|
||||
for (const auto &level : flags_levels_override) {
|
||||
if (level < lowest_global_level && level != Debug::DebugLevel::NOISE) lowest_global_level = level;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Debug::commitConfig()
|
||||
{
|
||||
active_streams = move(preparing_streams);
|
||||
auto agent_mode = Singleton::Consume<I_AgentDetails>::by<Debug>()->getOrchestrationMode();
|
||||
if (
|
||||
(agent_mode == OrchestrationMode::OFFLINE || agent_mode == OrchestrationMode::HYBRID) &&
|
||||
active_streams.find("FOG") != active_streams.end()
|
||||
) {
|
||||
active_streams.erase("FOG");
|
||||
}
|
||||
|
||||
if (active_streams.size() == 0) {
|
||||
active_streams["STDOUT"] = make_shared<DebugStream>(&cout);
|
||||
}
|
||||
|
||||
global_flags_levels = move(preparing_global_flags);
|
||||
lowest_global_level = global_flags_levels[Debug::DebugFlags::D_ALL];
|
||||
for (const auto &level : global_flags_levels) {
|
||||
if (level < lowest_global_level) lowest_global_level = level;
|
||||
}
|
||||
|
||||
applyOverrides();
|
||||
}
|
||||
|
||||
void
|
||||
Debug::failOpenDebugMode(chrono::seconds debug_period)
|
||||
{
|
||||
static int debug_routine_counter = 0;
|
||||
static FlagsArray global_flags_temp(global_flags_levels);
|
||||
static Debug::DebugLevel lowest_global_level_temp = lowest_global_level;
|
||||
|
||||
if (debug_period == chrono::seconds::zero()) return;
|
||||
|
||||
is_fail_open_mode = true;
|
||||
debug_routine_counter++;
|
||||
if (debug_routine_counter == 1) {
|
||||
global_flags_temp = global_flags_levels;
|
||||
lowest_global_level_temp = lowest_global_level;
|
||||
|
||||
global_flags_levels.fill(DebugLevel::NOISE);
|
||||
lowest_global_level = DebugLevel::NOISE;
|
||||
}
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
[debug_period] ()
|
||||
{
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::by<Debug>();
|
||||
mainloop->yield(debug_period);
|
||||
if (debug_routine_counter == 1) {
|
||||
is_fail_open_mode = false;
|
||||
global_flags_levels = move(global_flags_temp);
|
||||
lowest_global_level = lowest_global_level_temp;
|
||||
}
|
||||
debug_routine_counter--;
|
||||
},
|
||||
"Debug fail open handler",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
Debug::evalFlagByFlag(Debug::DebugLevel level, Debug::DebugFlags flag)
|
||||
{
|
||||
if (flags_levels_override[flag] != Debug::DebugLevel::NOISE) return flags_levels_override[flag] <= level;
|
||||
return global_flags_levels[flag] <= level;
|
||||
}
|
||||
|
||||
void
|
||||
Debug::setNewDefaultStdout(ostream *new_stream)
|
||||
{
|
||||
active_streams["STDOUT"] = make_shared<Debug::DebugStream>(new_stream);
|
||||
|
||||
if (active_streams.find("FOG") != active_streams.end()) {
|
||||
active_streams.erase("FOG");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Debug::isFlagAtleastLevel(Debug::DebugFlags flag, Debug::DebugLevel level)
|
||||
{
|
||||
return global_flags_levels[flag] <= level;
|
||||
}
|
||||
|
||||
void
|
||||
Debug::setUnitTestFlag(Debug::DebugFlags flag, Debug::DebugLevel level)
|
||||
{
|
||||
if (lowest_global_level > level) lowest_global_level = level;
|
||||
global_flags_levels[flag] = level;
|
||||
default_config.streams_in_context[0].flag_values[flag] = level;
|
||||
|
||||
for (DebugStreamConfiguration stream : default_config.streams_in_context) {
|
||||
if (stream.stream_name == "FOG") {
|
||||
stream.flag_values.fill(Debug::DebugLevel::NONE);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
Debug::findDebugFilePrefix(const string &file_name)
|
||||
{
|
||||
string log_files_prefix = getLogFilesPathConfig() + "/";
|
||||
static const vector<string> allowed_debug_file_prefixes({ "/tmp/", "/var/log/", log_files_prefix });
|
||||
for (const string &single_prefix : allowed_debug_file_prefixes) {
|
||||
if (file_name.find(single_prefix) == 0) return single_prefix;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void
|
||||
Debug::addActiveStream(const string &name)
|
||||
{
|
||||
auto stream_entry = active_streams.find(name);
|
||||
if (stream_entry != active_streams.end()) {
|
||||
current_active_streams.insert(stream_entry->second);
|
||||
}
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START - function is covered in unit-test, but not detected bt gcov
|
||||
void
|
||||
Debug::printBacktraceBeforeAbort()
|
||||
{
|
||||
if (!Singleton::exists<I_SignalHandler>()) return;
|
||||
|
||||
Maybe<vector<string>> bt_strings = Singleton::Consume<I_SignalHandler>::by<Debug>()->getBacktrace();
|
||||
if (!bt_strings.ok()) {
|
||||
stream << "\nNo backtrace to present";
|
||||
return;
|
||||
}
|
||||
|
||||
stream << "\nPresenting backtrace:";
|
||||
for (const string &bt_line : bt_strings.unpack()) {
|
||||
stream << "\n" << bt_line;
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
void
|
||||
Debug::startStreams(
|
||||
const DebugLevel &level,
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
const uint &line
|
||||
)
|
||||
{
|
||||
for (auto &added_stream : current_active_streams) {
|
||||
added_stream->printHeader(time, env, mainloop, level, file_name, func_name, line);
|
||||
stream.addStream(added_stream->getStream());
|
||||
}
|
||||
|
||||
is_debug_running = true;
|
||||
}
|
||||
|
||||
Debug::DebugLevel Debug::lowest_global_level = default_level;
|
||||
I_TimeGet *Debug::time = nullptr;
|
||||
I_MainLoop *Debug::mainloop = nullptr;
|
||||
I_Environment *Debug::env = nullptr;
|
||||
bool Debug::is_debug_running = false;
|
||||
bool Debug::is_fail_open_mode = false;
|
||||
bool Debug::debug_override_exist = false;
|
||||
string Debug::default_debug_file_stream_path = "";
|
||||
vector<string> Debug::streams_from_mgmt;
|
||||
137
core/debug_is/debug_ex.h
Normal file
137
core/debug_is/debug_ex.h
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __DEBUG_EX_H__
|
||||
#define __DEBUG_EX_H__
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "report/report.h"
|
||||
#include "i_agent_details.h"
|
||||
#include "i_environment.h"
|
||||
#include "i_mainloop.h"
|
||||
#include "report/report_bulks.h"
|
||||
|
||||
enum class Debug::DebugFlags
|
||||
{
|
||||
D_ALL,
|
||||
|
||||
#define DEFINE_FLAG(flag_name, parent_name) \
|
||||
flag_name,
|
||||
#include "debug_flags.h"
|
||||
#undef DEFINE_FLAG
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
class Debug::DebugStream
|
||||
{
|
||||
public:
|
||||
DebugStream(std::ostream *_stream) : stream(_stream) {}
|
||||
virtual ~DebugStream() {}
|
||||
|
||||
virtual void
|
||||
printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *mainloop,
|
||||
DebugLevel curr_level,
|
||||
const std::string &file_name,
|
||||
const std::string &func_name,
|
||||
uint line
|
||||
);
|
||||
|
||||
virtual void finishMessage() { *stream << std::endl; }
|
||||
|
||||
std::ostream * getStream() const { return stream; }
|
||||
|
||||
private:
|
||||
std::ostream *stream;
|
||||
};
|
||||
|
||||
class DebugFileStream : public Debug::DebugStream
|
||||
{
|
||||
public:
|
||||
DebugFileStream(const std::string &_file_name);
|
||||
~DebugFileStream();
|
||||
|
||||
void
|
||||
printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *mainloop,
|
||||
Debug::DebugLevel curr_level,
|
||||
const std::string &file_name,
|
||||
const std::string &func_name,
|
||||
uint line
|
||||
) override;
|
||||
|
||||
void finishMessage() override;
|
||||
|
||||
private:
|
||||
void openDebugFile();
|
||||
void closeDebugFile();
|
||||
bool retryFinishMessage();
|
||||
|
||||
std::string file_name;
|
||||
std::ofstream file;
|
||||
};
|
||||
|
||||
class DebugFogStream
|
||||
:
|
||||
public Debug::DebugStream,
|
||||
Singleton::Consume<I_AgentDetails>,
|
||||
Singleton::Consume<I_Environment>
|
||||
{
|
||||
public:
|
||||
DebugFogStream();
|
||||
~DebugFogStream();
|
||||
|
||||
void
|
||||
printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *mainloop,
|
||||
Debug::DebugLevel curr_level,
|
||||
const std::string &file_name,
|
||||
const std::string &func_name,
|
||||
uint line
|
||||
) override;
|
||||
|
||||
void finishMessage() override;
|
||||
|
||||
private:
|
||||
void sendBufferedMessages();
|
||||
void sendSingleMessage(const LogRest &report);
|
||||
void handleThresholdReach();
|
||||
|
||||
ReportIS::Severity getSeverity() const;
|
||||
ReportIS::LogLevel getLogLevel() const;
|
||||
|
||||
ReportsBulk reports;
|
||||
I_MainLoop::RoutineID debug_send_routine = 0;
|
||||
|
||||
std::stringstream message;
|
||||
std::set<ReportIS::Tags> tags;
|
||||
Debug::DebugLevel level;
|
||||
std::chrono::microseconds curr_time;
|
||||
std::string file_name;
|
||||
std::string func_name;
|
||||
std::string trace_id;
|
||||
std::string span_id;
|
||||
uint line;
|
||||
};
|
||||
|
||||
#endif // __DEBUG_EX_H__
|
||||
6
core/debug_is/debug_is_ut/CMakeLists.txt
Normal file
6
core/debug_is/debug_is_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
include_directories(${CMAKE_SOURCE_DIR}/components/include)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/cptest/include)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(debug_is_ut "debug_ut.cc" "agent_details;metric;event_is;-lboost_regex")
|
||||
1060
core/debug_is/debug_is_ut/debug_ut.cc
Executable file
1060
core/debug_is/debug_is_ut/debug_ut.cc
Executable file
File diff suppressed because it is too large
Load Diff
389
core/debug_is/debug_streams.cc
Normal file
389
core/debug_is/debug_streams.cc
Normal file
@@ -0,0 +1,389 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "debug_ex.h"
|
||||
|
||||
#include "i_time_get.h"
|
||||
#include "config.h"
|
||||
#include "i_messaging.h"
|
||||
#include "i_mainloop.h"
|
||||
#include "rest.h"
|
||||
#include "report/report.h"
|
||||
#include "report/log_rest.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ReportIS;
|
||||
|
||||
USE_DEBUG_FLAG(D_DEBUG_FOG);
|
||||
|
||||
static const int minimal_location_info_length = 60;
|
||||
static const int tracing_info_len = 6;
|
||||
static const int tracing_info_total_len = (2 * tracing_info_len) + 3;
|
||||
extern const string unnamed_service = "Unnamed Nano Service";
|
||||
|
||||
static const map<Debug::DebugLevel, string> prompt = {
|
||||
{ Debug::DebugLevel::NOISE, "***" },
|
||||
{ Debug::DebugLevel::TRACE, ">>>" },
|
||||
{ Debug::DebugLevel::DEBUG, "@@@" },
|
||||
{ Debug::DebugLevel::WARNING, "###" },
|
||||
{ Debug::DebugLevel::INFO, "---" },
|
||||
{ Debug::DebugLevel::ERROR, "!!!" },
|
||||
{ Debug::DebugLevel::ASSERTION, "~~~" }
|
||||
};
|
||||
|
||||
static string
|
||||
getTracingHeader(I_Environment *env)
|
||||
{
|
||||
auto current_trace = env->getCurrentTrace();
|
||||
if (current_trace.empty()) return ": ";
|
||||
string tracing_data;
|
||||
tracing_data.reserve(tracing_info_total_len);
|
||||
tracing_data.append(current_trace, 0, tracing_info_len);
|
||||
|
||||
auto current_span = env->getCurrentSpan();
|
||||
if (!current_span.empty()) {
|
||||
tracing_data += '-';
|
||||
tracing_data.append(current_span, 0, tracing_info_len);
|
||||
}
|
||||
tracing_data += ": ";
|
||||
return tracing_data;
|
||||
}
|
||||
|
||||
static string
|
||||
getCurrentRoutineHeader(I_MainLoop *mainloop)
|
||||
{
|
||||
auto current_routine_id = mainloop->getCurrentRoutineId();
|
||||
return current_routine_id.ok() ? "<" + to_string(*current_routine_id) + "> " : "";
|
||||
}
|
||||
|
||||
void
|
||||
Debug::DebugStream::printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *mainloop,
|
||||
DebugLevel curr_level,
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
uint line)
|
||||
{
|
||||
(*getStream()) << "[";
|
||||
if (time != nullptr) (*getStream()) << time->getWalltimeStr() << ": ";
|
||||
stringstream os;
|
||||
if (env != nullptr) os << getTracingHeader(env);
|
||||
if (mainloop != nullptr) os << getCurrentRoutineHeader(mainloop);
|
||||
os << func_name << '@' << file_name << ':' << line;
|
||||
stringstream location;
|
||||
location.width(minimal_location_info_length);
|
||||
location << left << os.str() << " | ";
|
||||
(*getStream()) << location.str() << prompt.at(curr_level) << "] ";
|
||||
}
|
||||
|
||||
DebugFileStream::DebugFileStream(const string &_file_name)
|
||||
:
|
||||
Debug::DebugStream(&file),
|
||||
file_name(_file_name)
|
||||
{
|
||||
openDebugFile();
|
||||
}
|
||||
|
||||
DebugFileStream::~DebugFileStream() { closeDebugFile(); }
|
||||
|
||||
void
|
||||
DebugFileStream::printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *mainloop,
|
||||
Debug::DebugLevel curr_level,
|
||||
const string &file_name,
|
||||
const string &func_name,
|
||||
uint line)
|
||||
{
|
||||
(*Debug::DebugStream::getStream()) << "[";
|
||||
if (time != nullptr) (*Debug::DebugStream::getStream()) << time->getWalltimeStr() << ": ";
|
||||
stringstream os;
|
||||
if (env != nullptr) os << getTracingHeader(env);
|
||||
if (mainloop != nullptr) os << getCurrentRoutineHeader(mainloop);
|
||||
|
||||
os << func_name << '@' << file_name << ':' << line;
|
||||
stringstream location;
|
||||
location.width(minimal_location_info_length);
|
||||
location << left << os.str() << " | ";
|
||||
(*Debug::DebugStream::getStream()) << location.str();
|
||||
(*Debug::DebugStream::getStream()) << prompt.at(curr_level) << "] ";
|
||||
}
|
||||
|
||||
void
|
||||
DebugFileStream::finishMessage()
|
||||
{
|
||||
file << endl;
|
||||
if (file.good()) return;
|
||||
|
||||
cerr
|
||||
<< "Failed to write debug message to file, re-opening debug file and retrying to write. File path: "
|
||||
<< file_name
|
||||
<< endl;
|
||||
|
||||
static const uint32_t max_num_retries = 3;
|
||||
for (uint32_t num_retries = 0; num_retries < max_num_retries; num_retries++) {
|
||||
|
||||
closeDebugFile();
|
||||
openDebugFile();
|
||||
file << endl;
|
||||
|
||||
if (file.good()) return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DebugFileStream::openDebugFile()
|
||||
{
|
||||
cerr << "Opening debug file. File path: " << file_name << endl;
|
||||
file.open(file_name, ofstream::app);
|
||||
if (!file.good()) {
|
||||
cerr << "Failed to open debug file. File path: " << file_name << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
cerr << "Successfully opened debug file. File path: " << file_name << endl;
|
||||
}
|
||||
|
||||
void
|
||||
DebugFileStream::closeDebugFile()
|
||||
{
|
||||
file.close();
|
||||
if (file.is_open() || file.failbit) {
|
||||
cerr << "Failed in closing debug file. File path: " << file_name << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
cerr << "Successfully closed debug file at path: " << file_name << endl;
|
||||
}
|
||||
|
||||
DebugFogStream::DebugFogStream()
|
||||
:
|
||||
DebugStream(&message)
|
||||
{
|
||||
if (!Singleton::exists<I_Messaging>() ||
|
||||
!Singleton::exists<Config::I_Config>() ||
|
||||
!Singleton::exists<I_MainLoop>()) {
|
||||
dbgError(D_DEBUG_FOG) << "Sending debugs to fog disabled due to missing components";
|
||||
return;
|
||||
}
|
||||
|
||||
reports.setBulkSize(getConfigurationWithDefault<uint>(100, "Debug I/S", "Debug bulk size"));
|
||||
|
||||
chrono::milliseconds sent_debug_bulk_interval_msec = chrono::milliseconds(
|
||||
getConfigurationWithDefault<uint>(
|
||||
30000,
|
||||
"Debug I/S",
|
||||
"Debug bulk sending interval in msec"
|
||||
)
|
||||
);
|
||||
auto mainloop = Singleton::Consume<I_MainLoop>::by<Debug>();
|
||||
debug_send_routine = mainloop->addRecurringRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
sent_debug_bulk_interval_msec,
|
||||
[this] () { sendBufferedMessages(); },
|
||||
"Debug Fog stream messaging"
|
||||
);
|
||||
}
|
||||
|
||||
DebugFogStream::~DebugFogStream()
|
||||
{
|
||||
if (!Singleton::exists<I_MainLoop>()) return;
|
||||
|
||||
if (Singleton::Consume<I_MainLoop>::by<Debug>()->doesRoutineExist(debug_send_routine)) {
|
||||
Singleton::Consume<I_MainLoop>::by<Debug>()->stop(debug_send_routine);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DebugFogStream::printHeader(
|
||||
I_TimeGet *time,
|
||||
I_Environment *env,
|
||||
I_MainLoop *,
|
||||
Debug::DebugLevel curr_level,
|
||||
const string &curr_file_name,
|
||||
const string &curr_func_name,
|
||||
uint curr_line)
|
||||
{
|
||||
message.str("");
|
||||
tags.clear();
|
||||
level = curr_level;
|
||||
file_name = curr_file_name;
|
||||
func_name = curr_func_name;
|
||||
line = curr_line;
|
||||
curr_time = time!=nullptr ? time->getWalltime() : chrono::microseconds(0);
|
||||
if (env != nullptr) {
|
||||
trace_id = env->getCurrentTrace();
|
||||
span_id = env->getCurrentSpan();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DebugFogStream::finishMessage()
|
||||
{
|
||||
string service_name = unnamed_service;
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto name = Singleton::Consume<I_Environment>::by<DebugFogStream>()->get<string>("Service Name");
|
||||
if (name.ok()) service_name = *name;
|
||||
}
|
||||
|
||||
AudienceTeam audience_team = AudienceTeam::NONE;
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto team = Singleton::Consume<I_Environment>::by<DebugFogStream>()->get<AudienceTeam>("Audience Team");
|
||||
if (team.ok()) audience_team = *team;
|
||||
}
|
||||
|
||||
Report message_to_fog(
|
||||
"Debug message",
|
||||
curr_time,
|
||||
Type::CODE,
|
||||
Level::LOG,
|
||||
getLogLevel(),
|
||||
Audience::INTERNAL,
|
||||
audience_team,
|
||||
getSeverity(),
|
||||
Priority::LOW,
|
||||
chrono::seconds(0),
|
||||
LogField("agentId", Singleton::Consume<I_AgentDetails>::by<DebugFogStream>()->getAgentId()),
|
||||
LogField("issuingFunction", func_name),
|
||||
LogField("issuingFile", file_name),
|
||||
LogField("issuingLine", line),
|
||||
tags,
|
||||
Tags::INFORMATIONAL
|
||||
);
|
||||
message_to_fog << LogField("eventMessage", message.str());
|
||||
|
||||
if (!getConfigurationWithDefault<bool>(true, "Debug I/S", "Enable bulk of debugs")) {
|
||||
LogRest rest(move(message_to_fog));
|
||||
Singleton::Consume<I_MainLoop>::by<Debug>()->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::Offline,
|
||||
[this, rest] () { sendSingleMessage(rest); },
|
||||
"Debug Fog stream messaging"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
reports.push(move(message_to_fog));
|
||||
}
|
||||
|
||||
void
|
||||
DebugFogStream::sendBufferedMessages()
|
||||
{
|
||||
auto threshold_bulk_size = getConfigurationWithDefault<uint>(300, "Debug I/S", "Threshold debug bulk size");
|
||||
if (reports.size() >= threshold_bulk_size) {
|
||||
handleThresholdReach();
|
||||
}
|
||||
|
||||
string fog_debug_uri = getConfigurationWithDefault<string>(
|
||||
"/api/v1/agents/events/bulk",
|
||||
"Debug I/S",
|
||||
"Fog debug URI"
|
||||
);
|
||||
|
||||
auto i_msg = Singleton::Consume<I_Messaging>::by<Debug>();
|
||||
|
||||
while (!reports.empty()) {
|
||||
auto rest = reports.pop();
|
||||
using Method = I_Messaging::Method;
|
||||
i_msg->sendObjectWithPersistence(rest, Method::POST, fog_debug_uri, "", true, MessageTypeTag::DEBUG);
|
||||
}
|
||||
}
|
||||
void
|
||||
DebugFogStream::sendSingleMessage(const LogRest &rest)
|
||||
{
|
||||
string fog_debug_uri = getConfigurationWithDefault<string>(
|
||||
"/api/v1/agents/events",
|
||||
"Debug I/S",
|
||||
"Fog debug URI"
|
||||
);
|
||||
|
||||
auto i_msg = Singleton::Consume<I_Messaging>::by<Debug>();
|
||||
i_msg->sendObjectWithPersistence(rest, I_Messaging::Method::POST, fog_debug_uri, "", true, MessageTypeTag::DEBUG);
|
||||
}
|
||||
|
||||
void
|
||||
DebugFogStream::handleThresholdReach()
|
||||
{
|
||||
string service_name = unnamed_service;
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto name = Singleton::Consume<I_Environment>::by<DebugFogStream>()->get<string>("Service Name");
|
||||
if (name.ok()) service_name = *name;
|
||||
}
|
||||
|
||||
AudienceTeam audience_team = AudienceTeam::NONE;
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto team = Singleton::Consume<I_Environment>::by<DebugFogStream>()->get<AudienceTeam>("Audience Team");
|
||||
if (team.ok()) audience_team = *team;
|
||||
}
|
||||
|
||||
Report message_to_fog(
|
||||
"Debug message",
|
||||
curr_time,
|
||||
Type::CODE,
|
||||
Level::LOG,
|
||||
LogLevel::WARNING,
|
||||
Audience::INTERNAL,
|
||||
audience_team,
|
||||
Severity::MEDIUM,
|
||||
Priority::LOW,
|
||||
chrono::seconds(0),
|
||||
LogField("agentId", Singleton::Consume<I_AgentDetails>::by<DebugFogStream>()->getAgentId()),
|
||||
LogField("issuingFunction", __FUNCTION__),
|
||||
LogField("issuingFile", "debug_streams.cc"),
|
||||
LogField("issuingLine", __LINE__),
|
||||
tags,
|
||||
Tags::INFORMATIONAL
|
||||
);
|
||||
|
||||
auto msg = "Threshold bulk size was reached, " + to_string(reports.size()) + " debug messages were discarded";
|
||||
message_to_fog << LogField("eventMessage", msg);
|
||||
|
||||
reports.clear();
|
||||
reports.push(move(message_to_fog));
|
||||
}
|
||||
|
||||
Severity
|
||||
DebugFogStream::getSeverity() const
|
||||
{
|
||||
switch (level) {
|
||||
case Debug::DebugLevel::NOISE: return Severity::INFO;
|
||||
case Debug::DebugLevel::TRACE: return Severity::INFO;
|
||||
case Debug::DebugLevel::DEBUG: return Severity::LOW;
|
||||
case Debug::DebugLevel::WARNING: return Severity::MEDIUM;
|
||||
case Debug::DebugLevel::INFO: return Severity::MEDIUM;
|
||||
case Debug::DebugLevel::ERROR: return Severity::HIGH;
|
||||
case Debug::DebugLevel::ASSERTION: return Severity::CRITICAL;
|
||||
case Debug::DebugLevel::NONE: return Severity::CRITICAL;
|
||||
}
|
||||
|
||||
return Severity::CRITICAL;
|
||||
}
|
||||
|
||||
LogLevel
|
||||
DebugFogStream::getLogLevel() const
|
||||
{
|
||||
switch (level) {
|
||||
case Debug::DebugLevel::NOISE: dbgAssert(false) << "Impossible LogLevel 'Noise'"; break;
|
||||
case Debug::DebugLevel::TRACE: return LogLevel::TRACE;
|
||||
case Debug::DebugLevel::DEBUG: return LogLevel::DEBUG;
|
||||
case Debug::DebugLevel::WARNING: return LogLevel::WARNING;
|
||||
case Debug::DebugLevel::INFO: return LogLevel::INFO;
|
||||
case Debug::DebugLevel::ERROR: return LogLevel::ERROR;
|
||||
case Debug::DebugLevel::ASSERTION: return LogLevel::ERROR;
|
||||
case Debug::DebugLevel::NONE: dbgAssert(false) << "Impossible LogLevel 'None'"; break;
|
||||
}
|
||||
|
||||
return LogLevel::INFO;
|
||||
}
|
||||
7
core/encryptor/CMakeLists.txt
Normal file
7
core/encryptor/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
add_subdirectory(cpnano_base64)
|
||||
|
||||
ADD_DEFINITIONS(-Wno-deprecated-declarations)
|
||||
|
||||
add_library(encryptor encryptor.cc "cpnano_base64/base64.cc")
|
||||
|
||||
add_subdirectory(encryptor_ut)
|
||||
6
core/encryptor/cpnano_base64/CMakeLists.txt
Executable file
6
core/encryptor/cpnano_base64/CMakeLists.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
add_executable(cpnano_base64 cpnano_base64.cc base64.cc)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}")
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_LINK_FLAGS}")
|
||||
|
||||
install(PROGRAMS $<TARGET_FILE:cpnano_base64> DESTINATION bin/)
|
||||
install(PROGRAMS $<TARGET_FILE:cpnano_base64> DESTINATION orchestration/)
|
||||
68
core/encryptor/cpnano_base64/base64.cc
Executable file
68
core/encryptor/cpnano_base64/base64.cc
Executable file
@@ -0,0 +1,68 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const string Base64::base64_base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
string
|
||||
Base64::encodeBase64(const string &input)
|
||||
{
|
||||
string out;
|
||||
int val = 0, val_base = -6;
|
||||
for (unsigned char c : input) {
|
||||
val = (val << 8) + c;
|
||||
val_base += 8;
|
||||
while (val_base >= 0) {
|
||||
out.push_back(base64_base[(val >> val_base) & 0x3F]);
|
||||
val_base -= 6;
|
||||
}
|
||||
}
|
||||
// -6 indicates the number of bits to take from each character
|
||||
// (6 bits is enough to present a range of 0 to 63)
|
||||
if (val_base > -6) out.push_back(base64_base[((val << 8) >> (val_base + 8)) & 0x3F]);
|
||||
while (out.size() % 4) out.push_back('=');
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
string
|
||||
Base64::decodeBase64(const string &input)
|
||||
{
|
||||
vector<int> mapper(256, -1);
|
||||
for (int i = 0; i < 64; i++) mapper[base64_base[i]] = i;
|
||||
|
||||
string out;
|
||||
int val = 0, val_base = -8;
|
||||
for (unsigned char c : input) {
|
||||
if (mapper[c] == -1) break;
|
||||
val = (val << 6) + mapper[c];
|
||||
val_base += 6;
|
||||
if (val_base >= 0) {
|
||||
out.push_back(char((val >> val_base) & 0xFF));
|
||||
val_base -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
29
core/encryptor/cpnano_base64/base64.h
Executable file
29
core/encryptor/cpnano_base64/base64.h
Executable file
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __BASE64_H__
|
||||
#define __BASE64_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
class Base64
|
||||
{
|
||||
public:
|
||||
static std::string encodeBase64(const std::string &input);
|
||||
static std::string decodeBase64(const std::string &input);
|
||||
|
||||
private:
|
||||
static const std::string base64_base;
|
||||
};
|
||||
|
||||
#endif // __BASE64_H__
|
||||
52
core/encryptor/cpnano_base64/cpnano_base64.cc
Executable file
52
core/encryptor/cpnano_base64/cpnano_base64.cc
Executable file
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// LCOV_EXCL_START Reason: main func tested in systemtest
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
cerr << "No arguments were provided" << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// don't skip the whitespace while reading
|
||||
cin >> noskipws;
|
||||
// use stream iterators to copy the stream to a string
|
||||
istream_iterator<char> it(cin);
|
||||
istream_iterator<char> end;
|
||||
string input(it, end);
|
||||
|
||||
if (string(argv[1]) == "-d" || string(argv[1]) == "--decode") {
|
||||
cout << Base64::decodeBase64(input) << endl;
|
||||
} else if(string(argv[1]) == "-e" || string(argv[1]) == "--encode") {
|
||||
cout << Base64::encodeBase64(input) << endl;
|
||||
} else {
|
||||
cerr << "Argument provided is illegal (options are -d|-e). Provided arg: " << string(argv[1]) << endl;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
78
core/encryptor/encryptor.cc
Normal file
78
core/encryptor/encryptor.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "encryptor.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
#include <openssl/aes.h>
|
||||
|
||||
#include "cpnano_base64/base64.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const int bits = 256;
|
||||
|
||||
class Encryptor::Impl : Singleton::Provide<I_Encryptor>::From<Encryptor>
|
||||
{
|
||||
// Base64
|
||||
string base64Encode(const string &input) override;
|
||||
string base64Decode(const string &input) override;
|
||||
|
||||
// Obfuscating
|
||||
string obfuscateXor(const string &input) override;
|
||||
string obfuscateXorBase64(const string &input) override;
|
||||
};
|
||||
|
||||
string
|
||||
Encryptor::Impl::base64Encode(const string &input)
|
||||
{
|
||||
return Base64::encodeBase64(input);
|
||||
}
|
||||
|
||||
string
|
||||
Encryptor::Impl::base64Decode(const string &input)
|
||||
{
|
||||
return Base64::decodeBase64(input);
|
||||
}
|
||||
|
||||
string
|
||||
Encryptor::Impl::obfuscateXor(const string &input)
|
||||
{
|
||||
//Any chars will work
|
||||
static const string key = "CHECKPOINT";
|
||||
string output;
|
||||
for (size_t i = 0; i < input.size(); i++) {
|
||||
output.push_back(input[i] ^ key[i % key.size()]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
string
|
||||
Encryptor::Impl::obfuscateXorBase64(const string &input)
|
||||
{
|
||||
string obfuscated = obfuscateXor(input);
|
||||
return base64Encode(obfuscated);
|
||||
}
|
||||
|
||||
void
|
||||
Encryptor::preload()
|
||||
{
|
||||
registerExpectedConfiguration<string>("encryptor", "Data files directory");
|
||||
}
|
||||
|
||||
Encryptor::Encryptor() : Component("Encryptor"), pimpl(make_unique<Impl>()) {}
|
||||
Encryptor::~Encryptor() {}
|
||||
7
core/encryptor/encryptor_ut/CMakeLists.txt
Executable file
7
core/encryptor/encryptor_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,7 @@
|
||||
link_directories(${BOOST_ROOT}/lib)
|
||||
|
||||
add_unit_test(
|
||||
encryptor_ut
|
||||
"encryptor_ut.cc"
|
||||
"encryptor;config;singleton;metric;event_is;-lboost_context;-lboost_regex;-lresolv;-lcrypto"
|
||||
)
|
||||
85
core/encryptor/encryptor_ut/encryptor_ut.cc
Normal file
85
core/encryptor/encryptor_ut/encryptor_ut.cc
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "encryptor.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "cptest.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
class EncryptorTest : public Test
|
||||
{
|
||||
public:
|
||||
EncryptorTest()
|
||||
{
|
||||
i_encryptor = Singleton::Consume<I_Encryptor>::from(encryptor);
|
||||
}
|
||||
|
||||
~EncryptorTest() {}
|
||||
|
||||
I_Encryptor *i_encryptor;
|
||||
Encryptor encryptor;
|
||||
};
|
||||
|
||||
TEST_F(EncryptorTest, doNothing)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, registerExpectedConfig)
|
||||
{
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
::Environment env;
|
||||
ConfigComponent config;
|
||||
env.preload();
|
||||
encryptor.preload();
|
||||
env.init();
|
||||
setConfiguration(
|
||||
string("this is new dir path"),
|
||||
string("encryptor"),
|
||||
string("Data files directory")
|
||||
);
|
||||
EXPECT_THAT(
|
||||
getConfiguration<string>("encryptor", "Data files directory"),
|
||||
IsValue(string("this is new dir path"))
|
||||
);
|
||||
env.fini();
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, base64Decode)
|
||||
{
|
||||
EXPECT_EQ(i_encryptor->base64Decode(""), "");
|
||||
EXPECT_EQ(i_encryptor->base64Decode("SGVsbG8gV29ybGQh"), "Hello World!");
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, base64Encode)
|
||||
{
|
||||
EXPECT_EQ(i_encryptor->base64Encode(""), "");
|
||||
EXPECT_EQ(i_encryptor->base64Encode("Hello World!"), "SGVsbG8gV29ybGQh");
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, XOREncrypt)
|
||||
{
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor(""), string(""));
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor("ABCDEF"), string("\x2\xa\x6\x7\xe\x16"));
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor("CHECKPOINT"), string("\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 10));
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor("asdqweasdqwe"), string("\x22\x3b\x21\x32\x3c\x35\x2e\x3a\x2a\x25\x34\x2d"));
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, XORDecrypt)
|
||||
{
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor(""), string(""));
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor(string("\x2\xa\x6\x7\xe\x16")), "ABCDEF");
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor(string("\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0", 10)), "CHECKPOINT");
|
||||
EXPECT_EQ(i_encryptor->obfuscateXor(string("\x22\x3b\x21\x32\x3c\x35\x2e\x3a\x2a\x25")), "asdqweasdq");
|
||||
}
|
||||
|
||||
TEST_F(EncryptorTest, XORBase64Encrypt)
|
||||
{
|
||||
EXPECT_EQ(i_encryptor->obfuscateXorBase64(""), string(""));
|
||||
EXPECT_EQ(
|
||||
i_encryptor->obfuscateXorBase64(string("\x0b\x2d\x29\x2f\x24\x70\x18\x26\x3c\x38\x27\x69")), "SGVsbG8gV29ybGQh"
|
||||
);
|
||||
}
|
||||
3
core/environment/CMakeLists.txt
Normal file
3
core/environment/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(environment environment.cc context.cc parsing.cc base_evaluators.cc trace.cc span.cc param_attr.cc)
|
||||
|
||||
add_subdirectory(environment_ut)
|
||||
103
core/environment/base_evaluators.cc
Normal file
103
core/environment/base_evaluators.cc
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "environment_evaluator.h"
|
||||
#include "evaluator_registration.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace EnvironmentHelper;
|
||||
|
||||
class AllEvaluator : public EnvironmentEvaluator<bool>
|
||||
{
|
||||
public:
|
||||
AllEvaluator(const vector<string> ¶ms)
|
||||
{
|
||||
for (const auto ¶m : params) {
|
||||
conditions.push_back(getMatcher<bool>(param));
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool, Context::Error>
|
||||
evalVariable() const override
|
||||
{
|
||||
for (auto &cond : conditions) {
|
||||
auto res = cond->evalVariable();
|
||||
if (!res.ok()) return res;
|
||||
if (res.unpack() == false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string getName() { return "All"; }
|
||||
|
||||
private:
|
||||
vector<EvaluatorPtr<bool>> conditions;
|
||||
};
|
||||
|
||||
class AnyEvaluator : public EnvironmentEvaluator<bool>
|
||||
{
|
||||
public:
|
||||
AnyEvaluator(const vector<string> ¶ms)
|
||||
{
|
||||
for (const auto ¶m : params) {
|
||||
conditions.push_back(getMatcher<bool>(param));
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool, Context::Error>
|
||||
evalVariable() const override
|
||||
{
|
||||
for (auto &cond : conditions) {
|
||||
auto res = cond->evalVariable();
|
||||
if (!res.ok()) return res;
|
||||
if (res.unpack() == true) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string getName() { return "Any"; }
|
||||
|
||||
private:
|
||||
vector<EvaluatorPtr<bool>> conditions;
|
||||
};
|
||||
|
||||
class NotEvaluator : public EnvironmentEvaluator<bool>
|
||||
{
|
||||
public:
|
||||
NotEvaluator(const vector<string> ¶ms)
|
||||
{
|
||||
if (params.size() != 1) reportWrongNumberOfParams(getName(), params.size(), 1, 1);
|
||||
cond = getMatcher<bool>(params[0]);
|
||||
}
|
||||
|
||||
Maybe<bool, Context::Error>
|
||||
evalVariable() const override
|
||||
{
|
||||
auto res = cond->evalVariable();
|
||||
if (!res.ok()) return res;
|
||||
return !(res.unpack());
|
||||
}
|
||||
|
||||
static std::string getName() { return "Not"; }
|
||||
|
||||
private:
|
||||
EvaluatorPtr<bool> cond;
|
||||
};
|
||||
|
||||
void
|
||||
registerBaseEvaluators()
|
||||
{
|
||||
addMatcher<AllEvaluator>();
|
||||
addMatcher<AnyEvaluator>();
|
||||
addMatcher<NotEvaluator>();
|
||||
}
|
||||
101
core/environment/context.cc
Normal file
101
core/environment/context.cc
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "context.h"
|
||||
#include "i_environment.h"
|
||||
#include "singleton.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
Context::activate()
|
||||
{
|
||||
Singleton::Consume<I_Environment>::by<Context>()->registerContext(this);
|
||||
}
|
||||
|
||||
void
|
||||
Context::deactivate()
|
||||
{
|
||||
Singleton::Consume<I_Environment>::by<Context>()->unregisterContext(this);
|
||||
}
|
||||
|
||||
map<string, string>
|
||||
Context::getAllStrings(const EnvKeyAttr::ParamAttr ¶m) const
|
||||
{
|
||||
map<string, string> result;
|
||||
for (auto &entry : values) {
|
||||
if (entry.first.doesMatch(param)) {
|
||||
auto entry_value = entry.second->getString();
|
||||
if (entry_value.ok()) result[entry.first.first] = *entry_value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::string
|
||||
Context::convertToString(MetaDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MetaDataType::File: return "file";
|
||||
case MetaDataType::SubjectIpAddr: return "subjectIp";
|
||||
case MetaDataType::OtherIpAddr: return "otherIp";
|
||||
case MetaDataType::Port: return "port";
|
||||
case MetaDataType::Protocol: return "protocol";
|
||||
case MetaDataType::Service: return "service";
|
||||
case MetaDataType::User: return "user";
|
||||
case MetaDataType::Domain: return "domain";
|
||||
case MetaDataType::Url: return "url";
|
||||
case MetaDataType::Direction: return "direction";
|
||||
case MetaDataType::Email: return "email";
|
||||
case MetaDataType::COUNT:
|
||||
dbgAssert(false) << "COUNT is not a valid meta data type";
|
||||
}
|
||||
dbgAssert(false) << "Reached impossible case with type=" << static_cast<int>(type);
|
||||
return "";
|
||||
}
|
||||
|
||||
map<string, uint64_t>
|
||||
Context::getAllUints(const EnvKeyAttr::ParamAttr ¶m) const
|
||||
{
|
||||
map<string, uint64_t> result;
|
||||
for (auto &entry : values) {
|
||||
if (entry.first.doesMatch(param)) {
|
||||
auto entry_value = entry.second->getUint();
|
||||
if (entry_value.ok()) result[entry.first.first] = *entry_value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
map<string, bool>
|
||||
Context::getAllBools(const EnvKeyAttr::ParamAttr ¶m) const
|
||||
{
|
||||
map<string, bool> result;
|
||||
for (auto &entry : values) {
|
||||
if (entry.first.doesMatch(param)) {
|
||||
auto entry_value = entry.second->getBool();
|
||||
if (entry_value.ok()) result[entry.first.first] = *entry_value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ScopedContext::ScopedContext()
|
||||
{
|
||||
activate();
|
||||
}
|
||||
|
||||
ScopedContext::~ScopedContext()
|
||||
{
|
||||
deactivate();
|
||||
}
|
||||
421
core/environment/environment.cc
Normal file
421
core/environment/environment.cc
Normal file
@@ -0,0 +1,421 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "environment.h"
|
||||
#include "context.h"
|
||||
#include "singleton.h"
|
||||
#include "common.h"
|
||||
#include "debug.h"
|
||||
#include "evaluator_registration.h"
|
||||
#include "environment_evaluator.h"
|
||||
#include "i_rest_api.h"
|
||||
#include "rest.h"
|
||||
#include "environment/trace.h"
|
||||
#include "boost/uuid/uuid.hpp"
|
||||
#include "boost/uuid/uuid_generators.hpp"
|
||||
#include "boost/uuid/uuid_io.hpp"
|
||||
#include "config.h"
|
||||
#include "environment/tracing_metric.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_ENVIRONMENT);
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
|
||||
class Environment::Impl
|
||||
:
|
||||
public Singleton::Provide<I_Environment>::From<Environment>
|
||||
{
|
||||
public:
|
||||
void init();
|
||||
void fini();
|
||||
|
||||
void preload();
|
||||
|
||||
void setActiveTenant(const string &tenant_id) override;
|
||||
void unsetActiveTenant() override;
|
||||
|
||||
void registerContext(Context *ptr) override;
|
||||
void unregisterContext(Context *ptr) override;
|
||||
ActiveContexts createEnvironment() override;
|
||||
ActiveContexts saveEnvironment() override;
|
||||
void loadEnvironment(ActiveContexts &&env) override;
|
||||
Context & getConfigurationContext() override;
|
||||
|
||||
map<string, string> getAllStrings(const EnvKeyAttr::ParamAttr ¶ms) const override;
|
||||
map<string, uint64_t> getAllUints(const EnvKeyAttr::ParamAttr ¶ms) const override;
|
||||
map<string, bool> getAllBools(const EnvKeyAttr::ParamAttr ¶ms) const override;
|
||||
|
||||
string getCurrentTrace() const override;
|
||||
string getCurrentSpan() const override;
|
||||
string getCurrentHeaders() override;
|
||||
|
||||
void startNewTrace(bool new_span, const string &_trace_id) override;
|
||||
void startNewSpan(Span::ContextType _type, const string &prev_span, const string &trace) override;
|
||||
using on_exit = scope_exit<function<void(void)>>;
|
||||
on_exit startNewSpanScope(Span::ContextType _type, const string &prev_span, const string &trace) override;
|
||||
void finishTrace(const string &trace) override;
|
||||
void finishSpan(const string &span) override;
|
||||
|
||||
private:
|
||||
const ActiveContexts & getActiveContexts() const override { return active_contexts; }
|
||||
void loadEnvConfig();
|
||||
|
||||
I_TenantManager *tenant_manager = nullptr;
|
||||
ActiveContexts active_contexts;
|
||||
Context global;
|
||||
map<string, TraceWrapper> active_traces;
|
||||
map<string, SpanWrapper> active_spans;
|
||||
map<string, int> tracing_stats;
|
||||
TracingMetric tracing_metric;
|
||||
TraceEvent trace_event;
|
||||
TraceFinishEvent trace_finish_event;
|
||||
bool is_metric_enabled = false;
|
||||
TracingStatus tracing_status = TracingStatus::OFF;
|
||||
bool was_initialized = false;
|
||||
};
|
||||
|
||||
class DeclareBooleanVariable : public ServerRest
|
||||
{
|
||||
public:
|
||||
void
|
||||
doCall() override
|
||||
{
|
||||
auto func = genEvaluator<bool>(expr);
|
||||
if (!func.ok()) {
|
||||
dbgWarning(D_ENVIRONMENT) << "Failed to generate boolean function: " << func.getErr();
|
||||
return;
|
||||
}
|
||||
dbgTrace(D_ENVIRONMENT) << "Boolean function was generated";
|
||||
auto env = Singleton::Consume<I_Environment>::from<Environment>();
|
||||
env->getConfigurationContext().registerFunc(name, func.unpackMove());
|
||||
}
|
||||
|
||||
private:
|
||||
C2S_PARAM(string, name);
|
||||
C2S_PARAM(string, expr);
|
||||
};
|
||||
|
||||
void
|
||||
Environment::Impl::loadEnvConfig()
|
||||
{
|
||||
auto tracing_conf = getConfigurationWithDefault<bool>(false, "environment", "enable tracing");
|
||||
if (tracing_status == TracingStatus::DISABLED) return;
|
||||
tracing_status = tracing_conf ? TracingStatus::ON : TracingStatus::OFF;
|
||||
if (tracing_status == TracingStatus::ON && !is_metric_enabled) {
|
||||
auto metric_report_interval = chrono::seconds(
|
||||
getConfigurationWithDefault<uint>(600, "environment", "tracingMetricReportInterval")
|
||||
);
|
||||
tracing_metric.init(
|
||||
"tracing",
|
||||
ReportIS::AudienceTeam::AGENT_CORE,
|
||||
ReportIS::IssuingEngine::AGENT_CORE,
|
||||
metric_report_interval,
|
||||
false
|
||||
);
|
||||
tracing_metric.registerListener();
|
||||
is_metric_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::init()
|
||||
{
|
||||
was_initialized = true;
|
||||
loadEnvConfig();
|
||||
if (!Singleton::exists<I_RestApi>()) return;
|
||||
auto rest = Singleton::Consume<I_RestApi>::by<Environment>();
|
||||
rest->addRestCall<DeclareBooleanVariable>(RestAction::ADD, "declare-boolean-variable");
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::fini()
|
||||
{
|
||||
global.deactivate();
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::setActiveTenant(const string &tenant_id)
|
||||
{
|
||||
if (tenant_manager == nullptr) tenant_manager = Singleton::Consume<I_TenantManager>::by<Environment>();
|
||||
tenant_manager->addActiveTenant(tenant_id);
|
||||
registerValue<string>("ActiveTenantId", tenant_id);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::unsetActiveTenant()
|
||||
{
|
||||
getConfigurationContext().unregisterKey<string>("ActiveTenantId");
|
||||
}
|
||||
|
||||
map<string, string>
|
||||
Environment::Impl::getAllStrings(const EnvKeyAttr::ParamAttr ¶ms) const
|
||||
{
|
||||
map<string, string> result;
|
||||
|
||||
for (auto &iter : active_contexts.first) {
|
||||
auto partial_results = iter->getAllStrings(params);
|
||||
for (auto &entry : partial_results) {
|
||||
result.emplace(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
map<string, uint64_t>
|
||||
Environment::Impl::getAllUints(const EnvKeyAttr::ParamAttr ¶ms) const
|
||||
{
|
||||
map<string, uint64_t> result;
|
||||
|
||||
for (auto &iter : active_contexts.first) {
|
||||
auto partial_results = iter->getAllUints(params);
|
||||
for (auto &entry : partial_results) {
|
||||
result.emplace(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
map<string, bool>
|
||||
Environment::Impl::getAllBools(const EnvKeyAttr::ParamAttr ¶ms) const
|
||||
{
|
||||
map<string, bool> result;
|
||||
|
||||
for (auto &iter : active_contexts.first) {
|
||||
auto partial_results = iter->getAllBools(params);
|
||||
for (auto &entry : partial_results) {
|
||||
result.emplace(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::registerContext(Context *ptr)
|
||||
{
|
||||
active_contexts.first.push_back(ptr);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::unregisterContext(Context *ptr)
|
||||
{
|
||||
dbgAssert(active_contexts.first.back() == ptr) <<
|
||||
"Contexts are supposed to unregister in reverse order to their registration";
|
||||
active_contexts.first.pop_back();
|
||||
}
|
||||
|
||||
string
|
||||
Environment::Impl::getCurrentTrace() const
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return "";
|
||||
|
||||
auto trace = get<string>("trace id");
|
||||
if (trace.ok()) return trace.unpack();
|
||||
return "";
|
||||
}
|
||||
|
||||
string
|
||||
Environment::Impl::getCurrentSpan() const
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return "";
|
||||
|
||||
auto span = get<string>("span id");
|
||||
if (span.ok()) return span.unpack();
|
||||
return "";
|
||||
}
|
||||
|
||||
string
|
||||
Environment::Impl::getCurrentHeaders()
|
||||
{
|
||||
string tracing_headers;
|
||||
auto trace_id = getCurrentTrace();
|
||||
if (!trace_id.empty()) {
|
||||
tracing_headers += "X-Trace-Id: " + trace_id + "\r\n";
|
||||
} else {
|
||||
string correlation_id_string = "00000000-0000-0000-0000-000000000000";
|
||||
try {
|
||||
boost::uuids::random_generator uuid_random_gen;
|
||||
correlation_id_string = boost::uuids::to_string(uuid_random_gen());
|
||||
} catch (const boost::uuids::entropy_error &e) {
|
||||
dbgTrace(D_ENVIRONMENT)
|
||||
<< "Failed to generate random correlation id - entropy exception. Exception: "
|
||||
<< e.what();
|
||||
tracing_status = TracingStatus::DISABLED;
|
||||
}
|
||||
tracing_headers += "X-Trace-Id: " + correlation_id_string + "\r\n";
|
||||
}
|
||||
|
||||
auto span_id = getCurrentSpan();
|
||||
if (!span_id.empty()) {
|
||||
tracing_headers += "X-Span-Id: " + span_id + "\r\n";
|
||||
}
|
||||
return tracing_headers;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::startNewTrace(bool new_span, const string &_trace_id)
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return;
|
||||
|
||||
try {
|
||||
TraceWrapper trace(_trace_id);
|
||||
auto trace_id = trace.getTraceId();
|
||||
active_traces.emplace(trace_id, trace);
|
||||
tracing_stats[trace_id] = 0;
|
||||
if (new_span) {
|
||||
SpanWrapper span(trace_id);
|
||||
auto span_id = span.getSpanId();
|
||||
active_spans.emplace(span_id, span);
|
||||
}
|
||||
trace_event.setTraceAmount(active_traces.size());
|
||||
trace_event.notify();
|
||||
|
||||
} catch (const boost::uuids::entropy_error &e) {
|
||||
tracing_status = TracingStatus::DISABLED;
|
||||
dbgWarning(D_TRACE)
|
||||
<< "Failed to generate random id - entropy exception. Exception: "
|
||||
<< e.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::startNewSpan(Span::ContextType context_type, const string &prev_span, const string &trace)
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return;
|
||||
|
||||
string selected_trace = !trace.empty() ? trace : getCurrentTrace();
|
||||
string selected_span = !prev_span.empty() ? prev_span : getCurrentSpan();
|
||||
try {
|
||||
SpanWrapper span(selected_trace, context_type, selected_span);
|
||||
active_spans.emplace(span.getSpanId(), span);
|
||||
} catch (const boost::uuids::entropy_error &e) {
|
||||
tracing_status = TracingStatus::DISABLED;
|
||||
dbgWarning(D_TRACE)
|
||||
<< "Failed to generate random id - entropy exception. Exception: "
|
||||
<< e.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::finishTrace(const string &trace)
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return;
|
||||
|
||||
auto deleted_trace = trace.empty() ? getCurrentTrace() : trace;
|
||||
if (deleted_trace.empty()) {
|
||||
dbgWarning(D_ENVIRONMENT) << "There is no current trace to finish";
|
||||
return;
|
||||
}
|
||||
|
||||
trace_finish_event.setSpanAmount(tracing_stats[deleted_trace]);
|
||||
active_traces.erase(deleted_trace);
|
||||
tracing_stats.erase(deleted_trace);
|
||||
|
||||
trace_event.setTraceAmount(active_traces.size());
|
||||
trace_event.notify();
|
||||
trace_finish_event.notify();
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::finishSpan(const string &span)
|
||||
{
|
||||
if (tracing_status != TracingStatus::ON) return;
|
||||
|
||||
auto deleted_span = span.empty() ? getCurrentSpan() : span;
|
||||
if (deleted_span.empty()) {
|
||||
dbgWarning(D_ENVIRONMENT) << "There is no current span to finish";
|
||||
return;
|
||||
}
|
||||
auto span_iter = active_spans.find(deleted_span);
|
||||
if (span_iter != active_spans.end()) {
|
||||
auto trace_id = span_iter->second.getTraceId();
|
||||
tracing_stats[trace_id]++;
|
||||
}
|
||||
|
||||
active_spans.erase(deleted_span);
|
||||
}
|
||||
|
||||
scope_exit<function<void(void)>>
|
||||
Environment::Impl::startNewSpanScope(Span::ContextType context_type, const string &prev_span, const string &trace)
|
||||
{
|
||||
startNewSpan(context_type, prev_span, trace);
|
||||
function<void(void)> release_function = [&] () { finishSpan(""); };
|
||||
return make_scope_exit(move(release_function));
|
||||
}
|
||||
|
||||
I_Environment::ActiveContexts
|
||||
Environment::Impl::createEnvironment()
|
||||
{
|
||||
return ActiveContexts({ &global }, Debug::DebugLockState::getState());
|
||||
}
|
||||
|
||||
I_Environment::ActiveContexts
|
||||
Environment::Impl::saveEnvironment()
|
||||
{
|
||||
return move(active_contexts);
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::loadEnvironment(ActiveContexts &&env)
|
||||
{
|
||||
active_contexts = move(env);
|
||||
Debug::DebugLockState::setState(active_contexts.second);
|
||||
}
|
||||
|
||||
Context &
|
||||
Environment::Impl::getConfigurationContext()
|
||||
{
|
||||
return global;
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Impl::preload()
|
||||
{
|
||||
registerBaseEvaluators();
|
||||
global.activate();
|
||||
registerExpectedConfiguration<bool>("environment", "enable tracing");
|
||||
registerExpectedConfiguration<uint>("environment", "tracingMetricReportInterval");
|
||||
registerConfigLoadCb(
|
||||
[this] ()
|
||||
{
|
||||
if (was_initialized) loadEnvConfig();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Environment::Environment() : Component("Environment"), pimpl(make_unique<Impl>()) {}
|
||||
|
||||
Environment::~Environment() {}
|
||||
|
||||
void
|
||||
Environment::init()
|
||||
{
|
||||
pimpl->init();
|
||||
}
|
||||
|
||||
void
|
||||
Environment::fini()
|
||||
{
|
||||
pimpl->fini();
|
||||
}
|
||||
|
||||
void
|
||||
Environment::preload()
|
||||
{
|
||||
pimpl->preload();
|
||||
}
|
||||
5
core/environment/environment_ut/CMakeLists.txt
Executable file
5
core/environment/environment_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
environment_ut
|
||||
"context_ut.cc;parsing_ut.cc;base_evaluators_ut.cc;environment_rest_ut.cc;span_ut.cc;trace_ut.cc;tracing_ut.cc;environment_ut.cc"
|
||||
"environment;singleton;rest;mainloop;metric;-lboost_context;event_is;-lboost_regex"
|
||||
)
|
||||
199
core/environment/environment_ut/base_evaluators_ut.cc
Executable file
199
core/environment/environment_ut/base_evaluators_ut.cc
Executable file
@@ -0,0 +1,199 @@
|
||||
#include "environment_evaluator.h"
|
||||
#include "cptest.h"
|
||||
#include "environment.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class EvaluatorTest : public Test
|
||||
{
|
||||
public:
|
||||
EvaluatorTest() {
|
||||
env.preload();
|
||||
env.init();
|
||||
}
|
||||
|
||||
~EvaluatorTest() {
|
||||
env.fini();
|
||||
}
|
||||
|
||||
private:
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
};
|
||||
|
||||
std::ostream & operator<<(std::ostream &os, const Context::Error &);
|
||||
|
||||
std::ostream &
|
||||
operator<<(std::ostream &os, const std::function<Maybe<bool, Context::Error>()> &)
|
||||
{
|
||||
return os;
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, empty_all)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("All()");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, empty_any)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Any()");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, not_true)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Not(All())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, not_false)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Not(Any())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, no_true_any)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Any(Any(),Any(),Any())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, one_true_any)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Any(Any(),All(),Any())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, one_false_all)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("All(All(),All(),Any())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, all_true_all)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("All(All(),All(),All())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, all_true_all_with_spaces)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("All(All() , All( ) ,All() )");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, select_all_all)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select(All(),All())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, select_all_any)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select(All(),Any())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, select_any_all)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select(Any(),All())");
|
||||
EXPECT_TRUE(eval.ok());
|
||||
auto function = eval.unpack();
|
||||
EXPECT_THAT(function(), IsValue(false));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, empty_not)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Not()");
|
||||
EXPECT_THAT(eval, IsError("Wrong number of parameters for 'Not'. Got 0 parameters instead of expected 1"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, too_many_inputs_not)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Not(Any(), Any())");
|
||||
EXPECT_THAT(eval, IsError("Wrong number of parameters for 'Not'. Got 2 parameters instead of expected 1"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, malformed_evaluator_leading_comma)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Any(, Any())");
|
||||
EXPECT_THAT(eval, IsError("Could not find the opening bracket in the string"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, malformed_evaluator_fake_evaluator)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Not(NOTHING())");
|
||||
EXPECT_THAT(eval, IsError("Evaluator 'NOTHING' doesn't exist for the required type"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, malformed_evaluator_fake_evaluator2)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("All(Any(), NOTHING())");
|
||||
EXPECT_THAT(eval, IsError("Evaluator 'NOTHING' doesn't exist for the required type"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, empty_get)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Get()");
|
||||
EXPECT_THAT(eval, IsError("Wrong number of parameters for 'Get'. Got 0 parameters instead of expected 1"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, empty_select)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select()");
|
||||
EXPECT_THAT(
|
||||
eval,
|
||||
IsError("Wrong number of parameters for 'Select'. Got 0 parameters instead of expected more than 2")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, single_in_select)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select(one)");
|
||||
EXPECT_THAT(
|
||||
eval,
|
||||
IsError("Wrong number of parameters for 'Select'. Got 1 parameters instead of expected more than 2")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, select_bad_evaluators)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Select(X(),Y())");
|
||||
EXPECT_THAT(eval, IsError("Evaluator 'X' doesn't exist for the required type"));
|
||||
}
|
||||
|
||||
TEST_F(EvaluatorTest, malformed_evaluator_within_parameter)
|
||||
{
|
||||
auto eval = genEvaluator<bool>("Any(All(), Any(BOB()))");
|
||||
EXPECT_THAT(eval, IsError("Evaluator 'BOB' doesn't exist for the required type"));
|
||||
}
|
||||
237
core/environment/environment_ut/context_ut.cc
Executable file
237
core/environment/environment_ut/context_ut.cc
Executable file
@@ -0,0 +1,237 @@
|
||||
#include "cptest.h"
|
||||
#include "context.h"
|
||||
#include "environment.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class ContextTest : public Test
|
||||
{
|
||||
public:
|
||||
Context ctx;
|
||||
|
||||
class TestObject
|
||||
{
|
||||
public:
|
||||
TestObject(const string &_x, int _y) : x(_x), y(_y) {};
|
||||
|
||||
bool
|
||||
operator==(const TestObject &rsh) const
|
||||
{
|
||||
return x == rsh.x && y == rsh.y;
|
||||
}
|
||||
|
||||
private:
|
||||
string x;
|
||||
int y;
|
||||
};
|
||||
|
||||
static Maybe<int, Context::Error> maybeIntFunc() { return 1; }
|
||||
static Maybe<double, Context::Error> maybeDoubleFunc() { return 1.1; }
|
||||
static Maybe<string, Context::Error> maybeStrFunc() { return string("str1"); }
|
||||
static Maybe<char, Context::Error> maybeCharFunc() { return 'a'; }
|
||||
static Maybe<TestObject, Context::Error> maybeObjectFunc() { return ContextTest::TestObject("test_object", 1); }
|
||||
static int intFunc() { return 2; }
|
||||
static double doubleFunc() { return 2.2; }
|
||||
static string strFunc() { return string("str2"); }
|
||||
static char charFunc() { return 'b'; }
|
||||
static TestObject objectFunc() { return ContextTest::TestObject("test_object", 2); }
|
||||
};
|
||||
|
||||
std::ostream &
|
||||
operator<<(std::ostream &os, const Context::Error &)
|
||||
{
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
operator<<(std::ostream &os, const ContextTest::TestObject &)
|
||||
{
|
||||
return os;
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_int)
|
||||
{
|
||||
ctx.registerValue("_int", 10);
|
||||
EXPECT_THAT(ctx.get<int>("_int"), IsValue(10));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_double)
|
||||
{
|
||||
ctx.registerValue("_double", 2.2);
|
||||
EXPECT_THAT(ctx.get<double>("_double"), IsValue(2.2));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_char)
|
||||
{
|
||||
ctx.registerValue("_char", 'a');
|
||||
EXPECT_THAT(ctx.get<char>("_char"), IsValue('a'));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_string)
|
||||
{
|
||||
ctx.registerValue("_string", string("string"));
|
||||
EXPECT_THAT(ctx.get<string>("_string"), IsValue("string"));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_object)
|
||||
{
|
||||
ctx.registerValue("_obj", ContextTest::TestObject("value", 1));
|
||||
EXPECT_THAT(ctx.get<TestObject>("_obj"), IsValue(ContextTest::TestObject("value", 1)));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_2_values_same_key)
|
||||
{
|
||||
ctx.registerValue("same_value_key", 1);
|
||||
ctx.registerValue("same_value_key", 2);
|
||||
EXPECT_THAT(ctx.get<int>("same_value_key"), IsValue(2));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_2_values_same_key_diff_context)
|
||||
{
|
||||
ConfigComponent conf;
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
::Environment env;
|
||||
auto i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
ctx.registerValue("same_value_key", 1);
|
||||
ctx.activate();
|
||||
EXPECT_THAT(i_env->get<int>("same_value_key"), IsValue(1));
|
||||
Context another_ctx;
|
||||
another_ctx.registerValue("same_value_key", 2);
|
||||
another_ctx.activate();
|
||||
EXPECT_THAT(i_env->get<int>("same_value_key"), IsValue(2));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_2_func_same_key)
|
||||
{
|
||||
ctx.registerFunc<int>("same_func_key", ContextTest::maybeIntFunc);
|
||||
ctx.registerFunc<double>("same_func_key", ContextTest::maybeDoubleFunc);
|
||||
EXPECT_THAT(ctx.get<double>("same_func_key"), IsValue(1.1));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_maybe_obj_func)
|
||||
{
|
||||
ctx.registerFunc<TestObject>("maybe_obj_func", ContextTest::maybeObjectFunc);
|
||||
EXPECT_THAT(ctx.get<TestObject>("maybe_obj_func"), IsValue(ContextTest::TestObject("test_object", 1)));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_maybe_int_func)
|
||||
{
|
||||
ctx.registerFunc<int>("maybe_int_func", ContextTest::maybeIntFunc);
|
||||
EXPECT_THAT(ctx.get<int>("maybe_int_func"), IsValue(1));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_maybe_str_func)
|
||||
{
|
||||
ctx.registerFunc<string>("maybe_str_func", ContextTest::maybeStrFunc);
|
||||
EXPECT_THAT(ctx.get<string>("maybe_str_func"), IsValue("str1"));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_maybe_double_func)
|
||||
{
|
||||
ctx.registerFunc<double>("maybe_double_func", ContextTest::maybeDoubleFunc);
|
||||
EXPECT_THAT(ctx.get<double>("maybe_double_func"), IsValue(1.1));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_maybe_char_func)
|
||||
{
|
||||
ctx.registerFunc<char>("maybe_char_func", ContextTest::maybeCharFunc);
|
||||
EXPECT_THAT(ctx.get<char>("maybe_char_func"), IsValue('a'));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_obj_func)
|
||||
{
|
||||
ctx.registerFunc<TestObject>("obj_func", ContextTest::objectFunc);
|
||||
EXPECT_THAT(ctx.get<TestObject>("obj_func"), IsValue(ContextTest::TestObject("test_object", 2)));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_int_func)
|
||||
{
|
||||
ctx.registerFunc<int>("int_func", ContextTest::intFunc);
|
||||
EXPECT_THAT(ctx.get<int>("int_func"), IsValue(2));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_str_func)
|
||||
{
|
||||
ctx.registerFunc<string>("str_func", ContextTest::strFunc);
|
||||
EXPECT_THAT(ctx.get<string>("str_func"), IsValue("str2"));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_double_func)
|
||||
{
|
||||
ctx.registerFunc<double>("double_func", ContextTest::doubleFunc);
|
||||
EXPECT_THAT(ctx.get<double>("double_func"), IsValue(2.2));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, register_return_char_func)
|
||||
{
|
||||
ctx.registerFunc<char>("char_func", ContextTest::charFunc);
|
||||
EXPECT_THAT(ctx.get<char>("char_func"), IsValue('b'));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, get_wrong_type_value)
|
||||
{
|
||||
ctx.registerValue("wrong_type", 1);
|
||||
EXPECT_THAT(ctx.get<string>("wrong_type"), IsError(Context::Error::NO_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, get_wrong_key_name)
|
||||
{
|
||||
ctx.registerValue("wrong_key", 1);
|
||||
EXPECT_THAT(ctx.get<int>("wrong_keyy"), IsError(Context::Error::NO_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, unregister_key_of_value)
|
||||
{
|
||||
ctx.registerValue("new_value_key", 1);
|
||||
ctx.unregisterKey<int>("new_value_key");
|
||||
EXPECT_THAT(ctx.get<int>("new_value_key"), IsError(Context::Error::NO_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(ContextTest, unregister_key_of_func)
|
||||
{
|
||||
ctx.registerFunc<int>("new_func_key", maybeIntFunc);
|
||||
ctx.unregisterKey<int>("new_func_key");
|
||||
EXPECT_THAT(ctx.get<int>("new_func_key"), IsError(Context::Error::NO_VALUE));
|
||||
}
|
||||
|
||||
TEST(ParamTest, matching)
|
||||
{
|
||||
using namespace EnvKeyAttr;
|
||||
|
||||
ParamAttr empty;
|
||||
ParamAttr verb1(Verbosity::LOW);
|
||||
ParamAttr verb2(Verbosity::HIGH);
|
||||
ParamAttr log(LogSection::SOURCE);
|
||||
ParamAttr both1(LogSection::SOURCE, Verbosity::LOW);
|
||||
ParamAttr both2(Verbosity::LOW, LogSection::SOURCE);
|
||||
ParamAttr both3(LogSection::SOURCE, Verbosity::HIGH);
|
||||
|
||||
|
||||
EXPECT_TRUE(empty.doesMatch(empty));
|
||||
EXPECT_TRUE(verb1.doesMatch(empty));
|
||||
EXPECT_TRUE(log.doesMatch(empty));
|
||||
EXPECT_TRUE(both1.doesMatch(empty));
|
||||
|
||||
EXPECT_FALSE(empty.doesMatch(verb1));
|
||||
EXPECT_FALSE(empty.doesMatch(log));
|
||||
EXPECT_FALSE(empty.doesMatch(both1));
|
||||
|
||||
EXPECT_TRUE(verb1.doesMatch(verb1));
|
||||
EXPECT_TRUE(both1.doesMatch(verb1));
|
||||
|
||||
EXPECT_FALSE(verb2.doesMatch(verb1));
|
||||
EXPECT_FALSE(log.doesMatch(verb1));
|
||||
EXPECT_FALSE(both3.doesMatch(verb1));
|
||||
|
||||
EXPECT_TRUE(both1.doesMatch(log));
|
||||
EXPECT_TRUE(both1.doesMatch(both1));
|
||||
EXPECT_TRUE(both1.doesMatch(both2));
|
||||
|
||||
EXPECT_FALSE(both1.doesMatch(both3));
|
||||
}
|
||||
68
core/environment/environment_ut/environment_rest_ut.cc
Normal file
68
core/environment/environment_ut/environment_rest_ut.cc
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "environment.h"
|
||||
#include "cptest.h"
|
||||
#include "mock/mock_rest_api.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
class EnvRestTest : public Test
|
||||
{
|
||||
public:
|
||||
EnvRestTest()
|
||||
{
|
||||
EXPECT_CALL(mock_rs, mockRestCall(RestAction::ADD, "declare-boolean-variable", _))
|
||||
.WillOnce(WithArg<2>(Invoke(this, &EnvRestTest::declareVariable)));
|
||||
env.preload();
|
||||
env.init();
|
||||
i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
}
|
||||
|
||||
unique_ptr<ServerRest> declare_variable;
|
||||
I_Environment *i_env;
|
||||
|
||||
private:
|
||||
bool declareVariable(const unique_ptr<RestInit> &p) { declare_variable = p->getRest(); return true; }
|
||||
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
StrictMock<MockRestApi> mock_rs;
|
||||
};
|
||||
|
||||
static ostream & operator<<(ostream &os, const Context::Error &) { return os; }
|
||||
|
||||
TEST_F(EnvRestTest, declare_variable)
|
||||
{
|
||||
EXPECT_THAT(i_env->get<bool>("true_val"), IsError(Context::Error::NO_VALUE));
|
||||
|
||||
stringstream is;
|
||||
is << "{\"name\": \"true_val\", \"expr\": \"All()\"}";
|
||||
auto output = declare_variable->performRestCall(is);
|
||||
|
||||
EXPECT_THAT(output, IsValue(""));
|
||||
|
||||
EXPECT_THAT(i_env->get<bool>("true_val"), IsValue(true));
|
||||
}
|
||||
|
||||
TEST_F(EnvRestTest, no_expr)
|
||||
{
|
||||
stringstream is;
|
||||
is << "{\"name\": \"true_val\"}";
|
||||
auto output = declare_variable->performRestCall(is);
|
||||
|
||||
EXPECT_THAT(output, IsError("Couldn't get variable expr"));
|
||||
}
|
||||
|
||||
TEST_F(EnvRestTest, no_name)
|
||||
{
|
||||
stringstream is;
|
||||
is << "{\"expr\": \"All()\"}";
|
||||
auto output = declare_variable->performRestCall(is);
|
||||
|
||||
EXPECT_THAT(output, IsError("Couldn't get variable name"));
|
||||
}
|
||||
87
core/environment/environment_ut/environment_ut.cc
Normal file
87
core/environment/environment_ut/environment_ut.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "environment.h"
|
||||
|
||||
#include "cptest.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace std;
|
||||
|
||||
class EnvironmentTest : public Test
|
||||
{
|
||||
public:
|
||||
EnvironmentTest() { ctx.activate(); ctx2.activate(); }
|
||||
~EnvironmentTest() { ctx2.deactivate(); ctx.deactivate(); }
|
||||
|
||||
::Environment env;
|
||||
I_Environment *i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
Context ctx, ctx2;
|
||||
};
|
||||
|
||||
class ConvertableToString
|
||||
{
|
||||
public:
|
||||
ConvertableToString(int _i, int _j) : i(_i), j(_j) {}
|
||||
|
||||
int i, j;
|
||||
};
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const ConvertableToString &obj)
|
||||
{
|
||||
return os << obj.i << "---" << obj.j;
|
||||
}
|
||||
|
||||
TEST_F(EnvironmentTest, all_strings)
|
||||
{
|
||||
string key_a = "A";
|
||||
string key_b = "B";
|
||||
string key_c = "C";
|
||||
string key_d = "D";
|
||||
string key_e = "E";
|
||||
string key_f = "F";
|
||||
string key_g = "G";
|
||||
|
||||
string val_num = "123";
|
||||
string val_alpha = "abc";
|
||||
|
||||
ctx.registerValue(key_a, val_num);
|
||||
ctx.registerValue(key_b, val_alpha, EnvKeyAttr::LogSection::SOURCE);
|
||||
ctx.registerValue(key_c, ConvertableToString(2, 9), EnvKeyAttr::LogSection::DATA, EnvKeyAttr::Verbosity::LOW);
|
||||
ctx.registerValue(key_g, false, EnvKeyAttr::Verbosity::HIGH);
|
||||
ctx2.registerValue(key_d, ConvertableToString(5, 3), EnvKeyAttr::LogSection::DATA);
|
||||
ctx2.registerValue(key_e, 9);
|
||||
ctx2.registerValue(key_f, true);
|
||||
|
||||
EXPECT_THAT(
|
||||
i_env->getAllStrings(),
|
||||
UnorderedElementsAre(
|
||||
make_pair(key_a, val_num),
|
||||
make_pair(key_b, val_alpha),
|
||||
make_pair(key_d, "5---3"),
|
||||
make_pair(key_c, "2---9")
|
||||
)
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
i_env->getAllStrings(EnvKeyAttr::Verbosity::HIGH),
|
||||
UnorderedElementsAre()
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
i_env->getAllStrings(EnvKeyAttr::LogSection::SOURCE),
|
||||
UnorderedElementsAre(
|
||||
make_pair(key_b, val_alpha)
|
||||
)
|
||||
);
|
||||
|
||||
EXPECT_THAT(
|
||||
i_env->getAllStrings(EnvKeyAttr::LogSection::DATA),
|
||||
UnorderedElementsAre(
|
||||
make_pair(key_d, "5---3"),
|
||||
make_pair(key_c, "2---9")
|
||||
)
|
||||
);
|
||||
|
||||
EXPECT_THAT(i_env->getAllUints(), UnorderedElementsAre(make_pair(key_e, 9)));
|
||||
|
||||
EXPECT_THAT(i_env->getAllBools(), UnorderedElementsAre(make_pair(key_f, true), make_pair(key_g, false)));
|
||||
}
|
||||
156
core/environment/environment_ut/parsing_ut.cc
Executable file
156
core/environment/environment_ut/parsing_ut.cc
Executable file
@@ -0,0 +1,156 @@
|
||||
#include "cptest.h"
|
||||
#include "environment_evaluator.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace EnvironmentHelper;
|
||||
|
||||
static const string error = "EvaluatorParseError not thrown as it should have been! Test Failed.";
|
||||
|
||||
TEST(ParsingTest, wrong_param_number_test_range)
|
||||
{
|
||||
try {
|
||||
reportWrongNumberOfParams("wrong_param_number_test_range", 4, 1, 3);
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
string output = e.getError();
|
||||
string expected = "Wrong number of parameters for 'wrong_param_number_test_range'. "
|
||||
"Got 4 parameters instead of expected between 1 and 3";
|
||||
EXPECT_EQ(output, expected);
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, wrong_param_number_test_min_eq_max)
|
||||
{
|
||||
try {
|
||||
reportWrongNumberOfParams("wrong_param_number_test_min_eq_max", 0, 1, 1);
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
string output = e.getError();
|
||||
string expected = "Wrong number of parameters for 'wrong_param_number_test_min_eq_max'. "
|
||||
"Got 0 parameters instead of expected 1";
|
||||
EXPECT_EQ(output, expected);
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, wrong_param_number_test_too_few)
|
||||
{
|
||||
try {
|
||||
reportWrongNumberOfParams("wrong_param_number_test_too_few", 0, 2, -1);
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
string output = e.getError();
|
||||
string expected = "Wrong number of parameters for 'wrong_param_number_test_too_few'. "
|
||||
"Got 0 parameters instead of expected more than 2";
|
||||
EXPECT_EQ(output, expected);
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, wrong_param_type_test)
|
||||
{
|
||||
try {
|
||||
reportWrongParamType("wrong_param_type_test", "bad_param", "good_reason");
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
string output = e.getError();
|
||||
string expected = "Parameter 'bad_param' for 'wrong_param_type_test' is of the "
|
||||
"wrong type because: good_reason";
|
||||
EXPECT_EQ(output, expected);
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, unkown_evaluator_type_test)
|
||||
{
|
||||
try {
|
||||
reportUnknownEvaluatorType("bad_eval");
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
string output = e.getError();
|
||||
string expected = "Evaluator 'bad_eval' doesn't exist for the required type";
|
||||
EXPECT_EQ(output, expected);
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_to_params_test_empty_input)
|
||||
{
|
||||
vector<string> res;
|
||||
EXPECT_EQ(breakEvaluatorString("()").second, res);
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_to_params_test_single_in)
|
||||
{
|
||||
vector<string> res = { "(X)" };
|
||||
EXPECT_EQ(breakEvaluatorString("((X))").second, res);
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_to_params_common_use)
|
||||
{
|
||||
vector<string> res = { "a", "1234 asd", "((1+2)*3)" };
|
||||
EXPECT_EQ(breakEvaluatorString("(a , 1234 asd ,((1+2)*3))").second, res);
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_to_params_test_commas_and_ignore_spaces)
|
||||
{
|
||||
vector<string> res = { "", "", "" };
|
||||
EXPECT_EQ(breakEvaluatorString("(,, , )").second, res);
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_to_params_bracket_games)
|
||||
{
|
||||
vector<string> res = { ") ,x x(()", ")))," };
|
||||
EXPECT_EQ(breakEvaluatorString("() ,x x((),))),)").second, res);
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_evaluator_string_test_empty_legal_input)
|
||||
{
|
||||
string normalized = "()";
|
||||
auto pair = breakEvaluatorString(normalized);
|
||||
string s = "";
|
||||
vector<string> v;
|
||||
|
||||
EXPECT_EQ(pair, make_pair(s, v));
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_evaluator_string_test_legal_input)
|
||||
{
|
||||
string normalized = "CMD((3 + 3 ) * 7 (), abc)";
|
||||
auto pair = breakEvaluatorString(normalized);
|
||||
string s = "CMD";
|
||||
vector<string> v = { "(3 + 3 ) * 7 ()", "abc" };
|
||||
|
||||
EXPECT_EQ(pair, make_pair(s, v));
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_evaluator_string_test_no_open_bracket)
|
||||
{
|
||||
try {
|
||||
breakEvaluatorString("EVALUATOR)");
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
EXPECT_EQ(e.getError(), "Could not find the opening bracket in the string");
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
|
||||
TEST(ParsingTest, break_evaluator_string_test_no_close_bracket)
|
||||
{
|
||||
try {
|
||||
breakEvaluatorString("EVALUATOR(x+1 = 3");
|
||||
}
|
||||
catch (EvaluatorParseError e) {
|
||||
EXPECT_EQ(e.getError(), "Could not find the closing bracket in the string");
|
||||
return;
|
||||
}
|
||||
ADD_FAILURE() << error;
|
||||
}
|
||||
95
core/environment/environment_ut/span_ut.cc
Executable file
95
core/environment/environment_ut/span_ut.cc
Executable file
@@ -0,0 +1,95 @@
|
||||
#include "environment/span.h"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "environment.h"
|
||||
#include "debug.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
|
||||
class SpanTest : public Test
|
||||
{
|
||||
public:
|
||||
SpanTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_TRACE, Debug::DebugLevel::TRACE);
|
||||
}
|
||||
|
||||
~SpanTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
getSpanValues(const T &span)
|
||||
{
|
||||
trace_id_str = span.getTraceId();
|
||||
span_id_str = span.getSpanId();
|
||||
prev_id_str = span.getPrevSpanId();
|
||||
type = span.getSpanContextType();
|
||||
}
|
||||
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
stringstream debug_output;
|
||||
string trace_id = "4cc6bce7-4f68-42d6-94fc-e4127ac65ded";
|
||||
string prev_span_id = "4cc6bce7-4f68-42d6-94fc-e4127ac65fef";
|
||||
string trace_id_str;
|
||||
string span_id_str;
|
||||
string prev_id_str;
|
||||
Span::ContextType type = Span::ContextType::NEW;
|
||||
};
|
||||
|
||||
TEST_F(SpanTest, newSpanInNewTraceTest)
|
||||
{
|
||||
{
|
||||
Span span(trace_id);
|
||||
getSpanValues<Span>(span);
|
||||
}
|
||||
|
||||
EXPECT_EQ(type, Span::ContextType::NEW);
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id "+ trace_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id_str));
|
||||
}
|
||||
|
||||
TEST_F(SpanTest, newSpanTest)
|
||||
{
|
||||
{
|
||||
Span span(trace_id, Span::ContextType::CHILD_OF, prev_span_id);
|
||||
getSpanValues<Span>(span);
|
||||
}
|
||||
|
||||
EXPECT_EQ(type, Span::ContextType::CHILD_OF);
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type Child of"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("previous span id " + prev_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id_str));
|
||||
}
|
||||
|
||||
TEST_F(SpanTest, newSpanWrapperTest)
|
||||
{
|
||||
{
|
||||
SpanWrapper span(trace_id, Span::ContextType::CHILD_OF, prev_span_id);
|
||||
getSpanValues<SpanWrapper>(span);
|
||||
}
|
||||
|
||||
EXPECT_EQ(type, Span::ContextType::CHILD_OF);
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type Child of"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("previous span id " + prev_id_str));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id_str));
|
||||
}
|
||||
59
core/environment/environment_ut/trace_ut.cc
Executable file
59
core/environment/environment_ut/trace_ut.cc
Executable file
@@ -0,0 +1,59 @@
|
||||
#include "environment/trace.h"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "environment.h"
|
||||
#include "debug.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
|
||||
class TraceTest : public Test
|
||||
{
|
||||
public:
|
||||
TraceTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_TRACE, Debug::DebugLevel::TRACE);
|
||||
}
|
||||
|
||||
~TraceTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
}
|
||||
|
||||
NiceMock<MockMainLoop> mock_mainloop;
|
||||
NiceMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
stringstream debug_output;
|
||||
};
|
||||
|
||||
TEST_F(TraceTest, defaultTraceTest)
|
||||
{
|
||||
string trace_id;
|
||||
{
|
||||
Trace trace;
|
||||
trace_id = trace.getTraceId();
|
||||
}
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
}
|
||||
|
||||
TEST_F(TraceTest, nonDefaultTraceTest)
|
||||
{
|
||||
string trace_id("4cc6bce7-4f68-42d6-94fc-e4127ac65ded");
|
||||
string trace_id_str;
|
||||
{
|
||||
Trace trace(trace_id);
|
||||
trace_id_str = trace.getTraceId();
|
||||
}
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
}
|
||||
384
core/environment/environment_ut/tracing_ut.cc
Executable file
384
core/environment/environment_ut/tracing_ut.cc
Executable file
@@ -0,0 +1,384 @@
|
||||
#include "environment.h"
|
||||
|
||||
#include "cptest.h"
|
||||
#include "i_mainloop.h"
|
||||
#include "mainloop.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "mock/mock_messaging.h"
|
||||
#include "mock/mock_agent_details.h"
|
||||
#include "config.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
USE_DEBUG_FLAG(D_METRICS);
|
||||
|
||||
class TracingTest : public Test
|
||||
{
|
||||
public:
|
||||
TracingTest()
|
||||
{
|
||||
env.preload();
|
||||
i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_TRACE, Debug::DebugLevel::TRACE);
|
||||
Debug::setUnitTestFlag(D_METRICS, Debug::DebugLevel::TRACE);
|
||||
setConfiguration<bool>(true, "environment", "enable tracing");
|
||||
setConfiguration<bool>(false, string("metric"), string("fogMetricSendEnable"));
|
||||
EXPECT_CALL(mock_mainloop, addRecurringRoutine(I_MainLoop::RoutineType::System, _, _, _, _))
|
||||
.WillOnce(DoAll(SaveArg<2>(&routine), Return(0)));
|
||||
env.init();
|
||||
}
|
||||
|
||||
~TracingTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
}
|
||||
|
||||
I_MainLoop::Routine routine;
|
||||
StrictMock<MockMainLoop> mock_mainloop;
|
||||
StrictMock<MockTimeGet> mock_timer;
|
||||
ConfigComponent conf;
|
||||
::Environment env;
|
||||
I_Environment *i_env;
|
||||
stringstream debug_output;
|
||||
};
|
||||
|
||||
TEST_F(TracingTest, noTraceTest)
|
||||
{
|
||||
auto empty_trace = i_env->getCurrentTrace();
|
||||
EXPECT_EQ("", empty_trace);
|
||||
|
||||
auto empty_span = i_env->getCurrentSpan();
|
||||
EXPECT_EQ("", empty_span);
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, disabledTraces)
|
||||
{
|
||||
setConfiguration<bool>(false, "environment", "enable tracing");
|
||||
env.init();
|
||||
|
||||
i_env->startNewTrace();
|
||||
auto trace_id = i_env->getCurrentTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_EQ("", debug_output.str());
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_EQ("", debug_output.str());
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_EQ("", debug_output.str());
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, newTraceSpanTest)
|
||||
{
|
||||
i_env->startNewTrace();
|
||||
auto trace_id = i_env->getCurrentTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(", trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(", context type New"));
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 1"));
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 0"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"maxSpanPerTrace\": 1"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"avgSpanPerTrace\": 1.0"));
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, newSpanScopeTest)
|
||||
{
|
||||
string trace_id;
|
||||
string span_id;
|
||||
i_env->startNewTrace(false);
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
trace_id = i_env->getCurrentTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
{
|
||||
auto span_scope = i_env->startNewSpanScope(Span::ContextType::NEW);
|
||||
span_id = i_env->getCurrentSpan();
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(", trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr(", context type New"));
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
}
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 0"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"maxSpanPerTrace\": 1"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"avgSpanPerTrace\": 1.0"));
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, oldTraceNewSpanTest)
|
||||
{
|
||||
i_env->startNewTrace(true, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
auto trace_id = i_env->getCurrentTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_EQ(trace_id, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, finishSpecificTraceSpan)
|
||||
{
|
||||
i_env->startNewTrace(true, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
auto trace_id = i_env->getCurrentTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_EQ(trace_id, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
|
||||
i_env->finishSpan(span_id);
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace(trace_id);
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(TracingTest, 2SpansSameFlow)
|
||||
{
|
||||
i_env->startNewTrace(true, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
auto trace_id = i_env->getCurrentTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_EQ(trace_id, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->startNewSpan(Span::ContextType::FOLLOWS_FROM, span_id);
|
||||
auto another_span_id = i_env->getCurrentSpan();
|
||||
EXPECT_EQ(trace_id, i_env->getCurrentTrace());
|
||||
EXPECT_NE(another_span_id, span_id);
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + another_span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type Follows from"));
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + another_span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 0"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"maxSpanPerTrace\": 2"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"avgSpanPerTrace\": 2.0"));
|
||||
}
|
||||
|
||||
TEST_F(TracingTest, metricTracingTest)
|
||||
{
|
||||
i_env->startNewTrace();
|
||||
auto span_id = i_env->getCurrentSpan();
|
||||
|
||||
i_env->finishSpan();
|
||||
|
||||
i_env->startNewSpan(Span::ContextType::FOLLOWS_FROM, span_id);
|
||||
auto another_span_id = i_env->getCurrentSpan();
|
||||
i_env->finishSpan();
|
||||
|
||||
i_env->startNewSpan(Span::ContextType::FOLLOWS_FROM, another_span_id);
|
||||
i_env->finishSpan();
|
||||
i_env->finishTrace();
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 0"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"maxSpanPerTrace\": 3"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"avgSpanPerTrace\": 3.0"));
|
||||
|
||||
|
||||
i_env->startNewTrace();
|
||||
i_env->finishSpan();
|
||||
i_env->finishTrace();
|
||||
|
||||
routine();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"Metric\": \"tracing\""));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"currentTraceNumber\": 0"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"maxSpanPerTrace\": 3"));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("\"avgSpanPerTrace\": 2.0"));
|
||||
}
|
||||
|
||||
class TracingCompRoutinesTest : public Test
|
||||
{
|
||||
public:
|
||||
TracingCompRoutinesTest()
|
||||
{
|
||||
env.preload();
|
||||
setConfiguration<bool>(true, "environment", "enable tracing");
|
||||
env.init();
|
||||
mainloop_comp.init();
|
||||
|
||||
mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
|
||||
i_env = Singleton::Consume<I_Environment>::from(env);
|
||||
|
||||
Debug::setNewDefaultStdout(&debug_output);
|
||||
Debug::setUnitTestFlag(D_TRACE, Debug::DebugLevel::TRACE);
|
||||
|
||||
I_MainLoop::Routine another_routine = [&] () {
|
||||
while (!stop) {
|
||||
mainloop->yield(true);
|
||||
}
|
||||
|
||||
i_env->startNewTrace(true, "a687b388-1108-4083-9852-07c33b107589");
|
||||
auto another_trace_id = i_env->getCurrentTrace();
|
||||
auto another_span_id = i_env->getCurrentSpan();
|
||||
|
||||
EXPECT_NE(trace_id, another_trace_id);
|
||||
EXPECT_NE(span_id, another_span_id);
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + another_trace_id));
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + another_span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + another_trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + another_span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + another_trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
};
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::RealTime,
|
||||
another_routine,
|
||||
"TracingCompRoutinesTest routine",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
~TracingCompRoutinesTest()
|
||||
{
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
mainloop_comp.fini();
|
||||
}
|
||||
|
||||
bool stop = false;
|
||||
stringstream debug_output;
|
||||
ConfigComponent config;
|
||||
NiceMock<MockAgentDetails> agent_details_mocker;
|
||||
NiceMock<MockTimeGet> mock_time;
|
||||
NiceMock<MockMessaging> mock_messaging;
|
||||
MainloopComponent mainloop_comp;
|
||||
::Environment env;
|
||||
I_MainLoop *mainloop;
|
||||
I_Environment *i_env;
|
||||
string trace_id;
|
||||
string span_id;
|
||||
};
|
||||
|
||||
TEST_F(TracingCompRoutinesTest, 2SpansDifFlow)
|
||||
{
|
||||
ON_CALL(mock_messaging, mockSendPersistentMessage(_, _, _, _, _, _, _)).WillByDefault(Return(string()));
|
||||
|
||||
I_MainLoop::Routine routine = [&] () {
|
||||
i_env->startNewTrace(true, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
trace_id = i_env->getCurrentTrace();
|
||||
span_id = i_env->getCurrentSpan();
|
||||
string headers = i_env->getCurrentHeaders();
|
||||
EXPECT_THAT(headers, HasSubstr("X-Trace-Id: " + trace_id));
|
||||
EXPECT_THAT(headers, HasSubstr("X-Span-Id: " + span_id));
|
||||
|
||||
EXPECT_EQ(trace_id, "a687b388-1108-4083-9852-07c33b1074e9");
|
||||
EXPECT_NE("", i_env->getCurrentSpan());
|
||||
EXPECT_NE("", i_env->getCurrentTrace());
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New trace was created " + trace_id));
|
||||
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("New span was created " + span_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("trace id " + trace_id));
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("context type New"));
|
||||
|
||||
stop = true;
|
||||
mainloop->yield(true);
|
||||
|
||||
i_env->finishSpan();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current span has ended " + span_id));
|
||||
EXPECT_EQ("", i_env->getCurrentSpan());
|
||||
|
||||
i_env->finishTrace();
|
||||
EXPECT_THAT(debug_output.str(), HasSubstr("Current trace has ended " + trace_id));
|
||||
EXPECT_EQ("", i_env->getCurrentTrace());
|
||||
};
|
||||
|
||||
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, routine, "2SpansDifFlow test routine");
|
||||
try {
|
||||
mainloop->run();
|
||||
} catch(...) {
|
||||
}
|
||||
}
|
||||
19
core/environment/evaluator_registration.h
Normal file
19
core/environment/evaluator_registration.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __EVALUATOR_REGISTRATION_H__
|
||||
#define __EVALUATOR_REGISTRATION_H__
|
||||
|
||||
void registerBaseEvaluators();
|
||||
|
||||
#endif // __EVALUATOR_REGISTRATION_H__
|
||||
23
core/environment/param_attr.cc
Normal file
23
core/environment/param_attr.cc
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "context.h"
|
||||
|
||||
bool
|
||||
EnvKeyAttr::ParamAttr::doesMatch(const EnvKeyAttr::ParamAttr ¶m) const
|
||||
{
|
||||
if (param.log_section != LogSection::NONE && param.log_section != log_section) return false;
|
||||
if (param.verbosity_level != Verbosity::NONE && param.verbosity_level != verbosity_level) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
137
core/environment/parsing.cc
Normal file
137
core/environment/parsing.cc
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "environment_evaluator.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "enum_range.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace EnvironmentHelper;
|
||||
|
||||
USE_DEBUG_FLAG(D_ENVIRONMENT);
|
||||
|
||||
void
|
||||
reportWrongNumberOfParams(const string &eval_name, uint no_params, uint min, uint max)
|
||||
{
|
||||
ostringstream os;
|
||||
os <<
|
||||
"Wrong number of parameters for '" <<
|
||||
eval_name <<
|
||||
"'. Got " <<
|
||||
no_params <<
|
||||
" parameters instead of expected ";
|
||||
if (min == max) {
|
||||
os << min;
|
||||
} else if (max == static_cast<uint>(-1)) {
|
||||
os << "more than " << min;
|
||||
} else {
|
||||
os << "between " << min << " and " << max;
|
||||
}
|
||||
dbgTrace(D_ENVIRONMENT) << os.str();
|
||||
EvaluatorParseError err(os.str());
|
||||
throw err;
|
||||
}
|
||||
|
||||
void
|
||||
reportWrongParamType(const string &eval_name, const string ¶m, const string &reason)
|
||||
{
|
||||
ostringstream os;
|
||||
os << "Parameter '" << param << "' for '" << eval_name << "' is of the wrong type because: " << reason;
|
||||
dbgTrace(D_ENVIRONMENT) << os.str();
|
||||
|
||||
EvaluatorParseError err(os.str());
|
||||
throw err;
|
||||
}
|
||||
|
||||
void
|
||||
reportUnknownEvaluatorType(const string &eval_name)
|
||||
{
|
||||
ostringstream os;
|
||||
os << "Evaluator '" << eval_name << "' doesn't exist for the required type";
|
||||
dbgTrace(D_ENVIRONMENT) << os.str();
|
||||
|
||||
EvaluatorParseError err(os.str());
|
||||
throw err;
|
||||
}
|
||||
|
||||
static string
|
||||
trim(const string &str)
|
||||
{
|
||||
auto first = str.find_first_not_of(' ');
|
||||
if (first == string::npos) return "";
|
||||
auto last = str.find_last_not_of(' ');
|
||||
return str.substr(first, (last-first+1));
|
||||
}
|
||||
|
||||
static vector<string>
|
||||
breakToParams(const string &list)
|
||||
{
|
||||
vector<string> res;
|
||||
|
||||
uint brackets = 0;
|
||||
uint start = 0;
|
||||
for (uint iter : makeRange(list.size())) {
|
||||
switch (list[iter]) {
|
||||
case ',': {
|
||||
if (brackets == 0) {
|
||||
res.push_back(trim(list.substr(start, iter-start)));
|
||||
start = iter + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '(': {
|
||||
brackets++;
|
||||
break;
|
||||
}
|
||||
case ')': {
|
||||
brackets--;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last section
|
||||
if (start < list.size()) res.push_back(trim(list.substr(start, list.size()-start)));
|
||||
|
||||
dbgTrace(D_ENVIRONMENT) << "Param vector size: " << res.size();
|
||||
return res;
|
||||
}
|
||||
|
||||
pair<string, vector<string>>
|
||||
EnvironmentHelper::breakEvaluatorString(const string &str)
|
||||
{
|
||||
auto trimmed = trim(str);
|
||||
|
||||
auto open_bracket = trimmed.find('(');
|
||||
if (open_bracket == string::npos) {
|
||||
dbgTrace(D_ENVIRONMENT) << "Could not find the opening bracket in the string";
|
||||
throw EvaluatorParseError("Could not find the opening bracket in the string");
|
||||
}
|
||||
auto close_bracket = trimmed.size() - 1;
|
||||
if (trimmed[close_bracket] != ')') {
|
||||
dbgTrace(D_ENVIRONMENT) << "Could not find the closing bracket in the string";
|
||||
throw EvaluatorParseError("Could not find the closing bracket in the string");
|
||||
}
|
||||
|
||||
auto command = trimmed.substr(0, open_bracket);
|
||||
auto params = trimmed.substr(open_bracket + 1, close_bracket - open_bracket - 1);
|
||||
|
||||
dbgTrace(D_ENVIRONMENT) << "Breaking evaluator string passed successfully";
|
||||
return make_pair(trim(command), breakToParams(trim(params)));
|
||||
}
|
||||
131
core/environment/span.cc
Executable file
131
core/environment/span.cc
Executable file
@@ -0,0 +1,131 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "environment/span.h"
|
||||
|
||||
#include "boost/uuid/uuid.hpp"
|
||||
#include "boost/uuid/uuid_generators.hpp"
|
||||
#include "boost/uuid/uuid_io.hpp"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace boost::uuids;
|
||||
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
|
||||
Span::Span(string _trace_id, ContextType _type, string _prev_span)
|
||||
:
|
||||
trace_id(_trace_id),
|
||||
context_type(_type),
|
||||
prev_span_id(_prev_span)
|
||||
{
|
||||
|
||||
if (trace_id.empty()) {
|
||||
dbgError(D_TRACE) << "Provided trace id is empty. Span cannot be created";
|
||||
return;
|
||||
}
|
||||
|
||||
if (context_type != ContextType::NEW && prev_span_id.empty()) {
|
||||
dbgError(D_TRACE) << "The provided previous span ID is empty. Cannot create span.";
|
||||
return;
|
||||
}
|
||||
|
||||
boost::uuids::random_generator uuid_random_gen;
|
||||
span_id = to_string(uuid_random_gen());
|
||||
|
||||
context.registerValue<string>("span id", span_id);
|
||||
context.activate();
|
||||
|
||||
dbgTrace(D_TRACE) << "New span was created " << span_id
|
||||
<< ", trace id " << trace_id
|
||||
<< ", context type " << convertSpanContextTypeToString(context_type)
|
||||
<< (context_type == ContextType::NEW ? string() : ", previous span id " + prev_span_id);
|
||||
}
|
||||
|
||||
Span::~Span()
|
||||
{
|
||||
dbgTrace(D_TRACE) << "Current span has ended " << span_id;
|
||||
context.unregisterKey<string>("span id");
|
||||
context.deactivate();
|
||||
}
|
||||
|
||||
string
|
||||
Span::getTraceId() const
|
||||
{
|
||||
return trace_id;
|
||||
}
|
||||
|
||||
string
|
||||
Span::getSpanId() const
|
||||
{
|
||||
return span_id;
|
||||
}
|
||||
|
||||
Span::ContextType
|
||||
Span::getSpanContextType() const
|
||||
{
|
||||
return context_type;
|
||||
}
|
||||
|
||||
string
|
||||
Span::getPrevSpanId() const
|
||||
{
|
||||
return prev_span_id;
|
||||
}
|
||||
|
||||
string
|
||||
Span::convertSpanContextTypeToString(ContextType type)
|
||||
{
|
||||
switch(type) {
|
||||
case ContextType::NEW: {
|
||||
return "New";
|
||||
}
|
||||
case ContextType::CHILD_OF: {
|
||||
return "Child of";
|
||||
}
|
||||
case ContextType::FOLLOWS_FROM: {
|
||||
return "Follows from";
|
||||
}
|
||||
}
|
||||
dbgAssert(false) << "Span context not supported";
|
||||
return string();
|
||||
}
|
||||
|
||||
SpanWrapper::SpanWrapper(string _trace_id, Span::ContextType _type, string _prev_span)
|
||||
:
|
||||
span(make_shared<Span>(_trace_id, _type, _prev_span))
|
||||
{}
|
||||
|
||||
string
|
||||
SpanWrapper::getTraceId() const
|
||||
{
|
||||
return span->getTraceId();
|
||||
}
|
||||
|
||||
string
|
||||
SpanWrapper::getSpanId() const
|
||||
{
|
||||
return span->getSpanId();
|
||||
}
|
||||
|
||||
Span::ContextType
|
||||
SpanWrapper::getSpanContextType() const
|
||||
{
|
||||
return span->getSpanContextType();
|
||||
}
|
||||
|
||||
string
|
||||
SpanWrapper::getPrevSpanId() const
|
||||
{
|
||||
return span->getPrevSpanId();
|
||||
}
|
||||
58
core/environment/trace.cc
Executable file
58
core/environment/trace.cc
Executable file
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "environment/trace.h"
|
||||
|
||||
#include "boost/uuid/uuid.hpp"
|
||||
#include "boost/uuid/uuid_generators.hpp"
|
||||
#include "boost/uuid/uuid_io.hpp"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace boost::uuids;
|
||||
|
||||
USE_DEBUG_FLAG(D_TRACE);
|
||||
|
||||
Trace::Trace(string _id)
|
||||
:
|
||||
trace_id(_id)
|
||||
{
|
||||
if (trace_id.empty()) {
|
||||
boost::uuids::random_generator uuid_random_gen;
|
||||
trace_id = to_string(uuid_random_gen());
|
||||
}
|
||||
context.registerValue<string>("trace id", trace_id);
|
||||
context.activate();
|
||||
dbgTrace(D_TRACE) << "New trace was created " << trace_id;
|
||||
}
|
||||
|
||||
Trace::~Trace()
|
||||
{
|
||||
dbgTrace(D_TRACE) << "Current trace has ended " << trace_id;
|
||||
context.unregisterKey<string>("trace id");
|
||||
context.deactivate();
|
||||
}
|
||||
|
||||
string
|
||||
Trace::getTraceId() const
|
||||
{
|
||||
return trace_id;
|
||||
}
|
||||
|
||||
TraceWrapper::TraceWrapper(string _id) : trace(make_shared<Trace>(_id)) {}
|
||||
|
||||
string
|
||||
TraceWrapper::getTraceId() const
|
||||
{
|
||||
return trace->getTraceId();
|
||||
}
|
||||
3
core/event_is/CMakeLists.txt
Normal file
3
core/event_is/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(event_is listener.cc)
|
||||
|
||||
add_Subdirectory(event_ut)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user