Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion userspace/libsinsp/filter/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static const std::vector<std::string> s_binary_list_ops = {
static constexpr const char* s_field_transformer_val = "val(";

static const std::vector<std::string> s_field_transformers =
{"tolower(", "toupper(", "b64(", "basename(", "len(", "join("};
{"tolower(", "toupper(", "b64(", "basename(", "len(", "join(", "concat("};

static inline void update_pos(const char c, ast::pos_info& pos) {
pos.col++;
Expand Down
49 changes: 49 additions & 0 deletions userspace/libsinsp/sinsp_filtercheck_multivalue_transformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,61 @@ bool sinsp_filter_multivalue_transformer_join::extract(sinsp_evt* evt,

sinsp_filter_multivalue_transformer_join::~sinsp_filter_multivalue_transformer_join() = default;

// concat

sinsp_filter_multivalue_transformer_concat::sinsp_filter_multivalue_transformer_concat(
std::vector<std::unique_ptr<sinsp_filter_check>> args):
sinsp_filter_multivalue_transformer({PT_CHARBUF, false}, std::move(args)) {
// Validate using argument_types()
const auto& arg_types = argument_types();

// concat requires at least 2 arguments
if(arg_types.size() < 2) {
throw sinsp_exception("concat() requires at least 2 arguments");
}

for(const auto& arg_t : arg_types) {
if(arg_t.type != PT_CHARBUF || arg_t.is_list) {
throw sinsp_exception("concat() arguments must be strings");
}
}
}

std::string
sinsp_filter_multivalue_transformer_concat::sinsp_filter_multivalue_transformer_concat::name()
const {
return "concat";
}

bool sinsp_filter_multivalue_transformer_concat::extract(sinsp_evt* evt,
std::vector<extract_value_t>& values,
bool sanitize_strings) {
m_res.clear();
for(const auto& arg : m_arguments) {
values.clear();
if(!arg->extract(evt, values, sanitize_strings)) {
return false;
}
m_res.append((char*)values[0].ptr);
}

extract_value_t val{(uint8_t*)m_res.c_str(), (uint32_t)m_res.length()};
values.clear();
values.push_back(val);
return true;
}

sinsp_filter_multivalue_transformer_concat::~sinsp_filter_multivalue_transformer_concat() = default;

std::unique_ptr<sinsp_filter_check> sinsp_filter_multivalue_transformer::create_transformer(
const std::string& name,
std::vector<std::unique_ptr<sinsp_filter_check>> args) {
if(name == "join") {
return std::make_unique<multivalue_transformer_filter_check>(
std::make_unique<sinsp_filter_multivalue_transformer_join>(std::move(args)));
} else if(name == "concat") {
return std::make_unique<multivalue_transformer_filter_check>(
std::make_unique<sinsp_filter_multivalue_transformer_concat>(std::move(args)));
} else {
throw std::runtime_error("unknown multivalue transformer");
}
Expand Down
17 changes: 17 additions & 0 deletions userspace/libsinsp/sinsp_filtercheck_multivalue_transformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,20 @@ class sinsp_filter_multivalue_transformer_join : public sinsp_filter_multivalue_
private:
std::string m_res;
};

// concat
class sinsp_filter_multivalue_transformer_concat : public sinsp_filter_multivalue_transformer {
public:
sinsp_filter_multivalue_transformer_concat(
std::vector<std::unique_ptr<sinsp_filter_check>> args);
virtual ~sinsp_filter_multivalue_transformer_concat();

std::string name() const;

virtual bool extract(sinsp_evt* evt,
std::vector<extract_value_t>& values,
bool sanitize_strings = true);

private:
std::string m_res;
};
53 changes: 53 additions & 0 deletions userspace/libsinsp/test/eventformatter.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,56 @@ TEST_F(sinsp_formatter_test, join_transformer) {
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["join(->,(proc.name,evt.arg.path))"], "init->/test/dir");
}

TEST_F(sinsp_formatter_test, concat_transformer) {
format("start %concat(proc.name, evt.arg.path) end");
EXPECT_EQ(m_last_res, true);
EXPECT_EQ(m_last_output, "start init/test/dir end");
EXPECT_EQ(m_last_field_values.size(), 3) << pretty_print(m_last_field_values);
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["concat(proc.name,evt.arg.path)"], "init/test/dir");
}

TEST_F(sinsp_formatter_test, concat_with_outer_transformer) {
format("start %toupper(concat(proc.name, evt.arg.path)) end");
EXPECT_EQ(m_last_res, true);
EXPECT_EQ(m_last_output, "start INIT/TEST/DIR end");
EXPECT_EQ(m_last_field_values.size(), 3) << pretty_print(m_last_field_values);
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["toupper(concat(proc.name,evt.arg.path))"], "INIT/TEST/DIR");
}

TEST_F(sinsp_formatter_test, concat_with_inner_transformer) {
format("start %concat(toupper(proc.name), evt.arg.path) end");
EXPECT_EQ(m_last_res, true);
EXPECT_EQ(m_last_output, "start INIT/test/dir end");
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
EXPECT_EQ(m_last_field_values["toupper(proc.name)"], "INIT");
EXPECT_EQ(m_last_field_values["concat(toupper(proc.name),evt.arg.path)"], "INIT/test/dir");
}

TEST_F(sinsp_formatter_test, join_with_inner_transformer) {
format("start %join(\"->\", (toupper(proc.name), tolower(evt.arg.path))) end");
EXPECT_EQ(m_last_res, true);
EXPECT_EQ(m_last_output, "start INIT->/test/dir end");
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
EXPECT_EQ(m_last_field_values["toupper(proc.name)"], "INIT");
EXPECT_EQ(m_last_field_values["join(->,(toupper(proc.name),tolower(evt.arg.path)))"],
"INIT->/test/dir");
}

TEST_F(sinsp_formatter_test, nested_concat_and_join) {
format("start %toupper(join(\"->\", (concat(proc.name, evt.arg.path), proc.name))) end");
EXPECT_EQ(m_last_res, true);
EXPECT_EQ(m_last_output, "start INIT/TEST/DIR->INIT end");
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
EXPECT_EQ(m_last_field_values["proc.name"], "init");
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
EXPECT_EQ(m_last_field_values["concat(proc.name,evt.arg.path)"], "init/test/dir");
EXPECT_EQ(m_last_field_values["toupper(join(->,(concat(proc.name,evt.arg.path),proc.name)))"],
"INIT/TEST/DIR->INIT");
}
3 changes: 2 additions & 1 deletion userspace/libsinsp/test/filter_parser.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ TEST(parser, supported_operators) {

TEST(parser, supported_field_transformers) {
std::string expected_val = "val";
std::vector<std::string> expected = {"tolower", "toupper", "b64", "basename", "len", "join"};
std::vector<std::string> expected =
{"tolower", "toupper", "b64", "basename", "len", "join", "concat"};

auto actual = parser::supported_field_transformers();
ASSERT_EQ(actual.size(), expected.size());
Expand Down
117 changes: 117 additions & 0 deletions userspace/libsinsp/test/filter_transformer.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,51 @@ TEST_F(sinsp_with_test_input, multivalue_transformer_join) {
EXPECT_THROW(eval_filter(evt, "join(\"a\", \"b\") = foo"), sinsp_exception);
}

TEST_F(sinsp_with_test_input, multivalue_transformer_concat) {
add_default_init_thread();
open_inspector();

sinsp_evt* evt;

int64_t dirfd = 3;
const char* file_to_run = "/tmp/file_to_run";

evt = add_event_advance_ts(increasing_ts(),
1,
PPME_SYSCALL_OPEN_X,
6,
dirfd,
file_to_run,
0,
0,
0,
(uint64_t)0);

EXPECT_TRUE(eval_filter(evt, "concat(fd.name, fd.directory) = /tmp/file_to_run/tmp"));
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = aaabbb"));
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\", \"ccc\") = aaabbbccc"));
EXPECT_TRUE(eval_filter(evt, "concat(fd.name, \"aaa\") = /tmp/file_to_runaaa"));
EXPECT_TRUE(
eval_filter(evt, "concat(fd.name, \"aaa\", fd.directory) = /tmp/file_to_runaaa/tmp"));
EXPECT_TRUE(eval_filter(evt, "concat(toupper(fd.name), \"aaa\") = /TMP/FILE_TO_RUNaaa"));
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", toupper(fd.name)) = aaa/TMP/FILE_TO_RUN"));
EXPECT_TRUE(eval_filter(
evt,
"concat(fd.directory, concat(\"aaa\", toupper(fd.name))) = /tmpaaa/TMP/FILE_TO_RUN"));
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaabbb\""));
EXPECT_FALSE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaa-bbb\""));

// Validation error tests
// concat() requires at least 2 arguments
EXPECT_THROW(eval_filter(evt, "concat() = foo"), sinsp_exception);
EXPECT_THROW(eval_filter(evt, "concat(\"aaa\") = foo"), sinsp_exception);
// concat() arguments must be strings (not lists)
EXPECT_THROW(eval_filter(evt, "concat(fd.types, \"a\") = foo"), sinsp_exception);
EXPECT_THROW(eval_filter(evt, "concat((\"a\", \"b\"), \"a\") = foo"), sinsp_exception);
EXPECT_THROW(eval_filter(evt, "concat(\"a\", (\"a\", \"b\")) = foo"), sinsp_exception);
EXPECT_THROW(eval_filter(evt, "concat((\"a\", \"b\"), (\"a\", \"b\")) = foo"), sinsp_exception);
}

TEST_F(sinsp_with_test_input, multivalue_transformer_with_outer_transformer) {
add_default_init_thread();
open_inspector();
Expand Down Expand Up @@ -519,6 +564,37 @@ TEST_F(sinsp_with_test_input, multivalue_transformer_with_outer_transformer) {
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) > 20"));
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) >= 21"));
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) < 22"));

// Apply toupper to concat result
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) = /TMP/FILE_TO_RUN/TMP"));
EXPECT_TRUE(eval_filter(evt, "toupper(concat(\"aaa\", \"bbb\")) = AAABBB"));

// Apply tolower to concat result
EXPECT_TRUE(eval_filter(evt, "tolower(concat(fd.name, fd.directory)) = /tmp/file_to_run/tmp"));
EXPECT_TRUE(eval_filter(evt, "tolower(concat(\"AAA\", \"BBB\")) = aaabbb"));

// Apply len to concat result
// fd.name = /tmp/file_to_run (16 chars), fd.directory = /tmp (4 chars)
// Total = 16 + 4 = 20
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) = 20"));
EXPECT_TRUE(eval_filter(evt, "len(concat(\"aaa\", \"bbb\")) = 6"));

// Apply b64 to concat result
// "aaabbb" in base64 is "YWFhYmJi"
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaabbb\""));
EXPECT_TRUE(eval_filter(evt, "b64(concat(\"YWFh\", \"YmJi\")) = \"aaabbb\""));

// Chain multiple transformers on concat result
EXPECT_TRUE(
eval_filter(evt,
"toupper(tolower(concat(fd.name, fd.directory))) = /TMP/FILE_TO_RUN/TMP"));

// Combine with comparison operators
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) contains /TMP"));
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) startswith /TMP"));
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) > 19"));
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) >= 20"));
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) < 21"));
}

TEST(multivalue_transformer, argument_types) {
Expand Down Expand Up @@ -584,3 +660,44 @@ TEST(multivalue_transformer, result_type) {
EXPECT_EQ(result.type, PT_CHARBUF);
EXPECT_FALSE(result.is_list);
}

TEST(multivalue_transformer_concat, argument_types) {
// Create arguments for concat: multiple strings
std::vector<std::unique_ptr<sinsp_filter_check>> args;

// First argument: a string
args.push_back(std::make_unique<rawstring_check>("aaa"));

// Second argument: another string
args.push_back(std::make_unique<rawstring_check>("bbb"));

// Third argument: another string
args.push_back(std::make_unique<rawstring_check>("ccc"));

// Create the concat transformer
sinsp_filter_multivalue_transformer_concat concat_transformer(std::move(args));

// Test argument_types()
const auto& arg_types = concat_transformer.argument_types();

ASSERT_EQ(arg_types.size(), 3);

// All arguments should be PT_CHARBUF and not lists
for(size_t i = 0; i < arg_types.size(); i++) {
EXPECT_EQ(arg_types[i].type, PT_CHARBUF);
EXPECT_FALSE(arg_types[i].is_list);
}
}

TEST(multivalue_transformer_concat, result_type) {
std::vector<std::unique_ptr<sinsp_filter_check>> args;
args.push_back(std::make_unique<rawstring_check>("aaa"));
args.push_back(std::make_unique<rawstring_check>("bbb"));

sinsp_filter_multivalue_transformer_concat concat_transformer(std::move(args));

// concat should return PT_CHARBUF and not a list
auto result = concat_transformer.result_type();
EXPECT_EQ(result.type, PT_CHARBUF);
EXPECT_FALSE(result.is_list);
}
Loading