First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

48
core/CMakeLists.txt Normal file
View 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
View 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.

View File

@@ -0,0 +1,3 @@
add_library(agent_core_utilities agent_core_utilities.cc)
add_subdirectory(agent_core_utilities_ut)

View 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 &regex)
{
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 &regex)
{
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 &regex)
{
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 &regex)
{
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 &regex)
{
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 &regex, 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

View File

@@ -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"
)

View File

@@ -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");
}

View File

@@ -0,0 +1,3 @@
add_library(agent_details agent_details.cc)
add_subdirectory(agent_details_ut)

View 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

View 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"
)

View 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);
}

View File

@@ -0,0 +1,3 @@
add_library(agent_details_reporter agent_details_reporter.cc agent_details_report.cc)
add_subdirectory(agent_details_reporter_ut)

View 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;
}

View 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");
}

View File

@@ -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"
)

View File

@@ -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);
}

View File

@@ -0,0 +1 @@
add_subdirectory(http_configuration)

View File

@@ -0,0 +1,3 @@
add_library(http_configuration http_configuration.cc)
add_subdirectory(http_configuration_ut)

View 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;
}
}

View File

@@ -0,0 +1 @@
add_unit_test(http_configuration_ut http_configuration_ut.cc http_configuration)

View File

@@ -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);
}

View 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
View 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;
}

View 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> &params)
:
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> &params) : Equal<Buffer>(params) {}
static std::string getName() { return Equal<Buffer>::getName() + "Buffer"; }
};
void
Buffer::preload()
{
addMatcher<ConstantBuffer>();
addMatcher<EqualBuffer>();
}

View 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"
)

View 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));
}

View 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
View 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
View 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
View 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;
}

View 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)

View 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;
}

View 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")

View 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")
);
}

View 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
View 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 &regex) 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 &regex) 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();
}

View 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);
}

View 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;
}

View 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__

View File

@@ -0,0 +1 @@
add_library(connkey connkey.cc connkey_eval.cc)

241
core/connkey/connkey.cc Executable file
View 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;
}

View 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> &params)
:
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> &params)
:
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> &params)
:
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> &params) : Equal<uint16_t>(params) {}
static string getName() { return Equal<uint16_t>::getName() + "Port"; }
};
class EqualIP : public Equal<IPAddr>
{
public:
EqualIP(const vector<string> &params) : Equal<IPAddr>(params) {}
static string getName() { return Equal<IPAddr>::getName() + "IP"; }
};
class EqualProtocol : public Equal<IPProto>
{
public:
EqualProtocol(const vector<string> &params) : Equal<IPProto>(params) {}
static string getName() { return Equal<IPProto>::getName() + "Protocol"; }
};
class DPort : public Invoker<uint16_t, ConnKey>
{
public:
DPort(const vector<string> &params)
:
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> &params)
:
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> &params)
:
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> &params)
:
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> &params)
:
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>();
}

View 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
View 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
View 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");
}

View 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);
}

View 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
View 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());
}

View 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));
}

View 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");
}

View 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
View 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
View 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
View 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);
}

View File

@@ -0,0 +1,5 @@
add_unit_test(
cptest_ut
"cptest_ut.cc;cptest_packet_ut.cc"
"buffers;packet;connkey;singleton;logging"
)

View 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));
}

View 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
View File

@@ -0,0 +1,3 @@
add_library(cpu cpu.cc)
add_subdirectory(cpu_ut)

330
core/cpu/cpu.cc Executable file
View 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 &current_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
View 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
View 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();
}

View 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
View 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
View 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__

View 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")

File diff suppressed because it is too large Load Diff

View 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;
}

View 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)

View 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/)

View 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;
}

View 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__

View 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

View 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() {}

View 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"
)

View 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"
);
}

View 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)

View 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> &params)
{
for (const auto &param : 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> &params)
{
for (const auto &param : 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> &params)
{
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
View 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 &param) 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 &param) 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 &param) 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();
}

View 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 &params) const override;
map<string, uint64_t> getAllUints(const EnvKeyAttr::ParamAttr &params) const override;
map<string, bool> getAllBools(const EnvKeyAttr::ParamAttr &params) 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 &params) 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 &params) 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 &params) 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();
}

View 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"
)

View 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"));
}

View 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));
}

View 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"));
}

View 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)));
}

View 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;
}

View 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));
}

View 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));
}

View 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(...) {
}
}

View 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__

View 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 &param) 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
View 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 &param, 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
View 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
View 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();
}

View 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