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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Build
- name: Build examples
run: make -j4

- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON

- name: Build tests
run: cmake --build build --parallel 4

- name: Run tests
run: ctest --test-dir build --output-on-failure
35 changes: 35 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (C) 2025 The Falco Authors.
#
# 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.

cmake_minimum_required(VERSION 3.12)
project(plugin-sdk-cpp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Header-only library
add_library(plugin-sdk-cpp INTERFACE)
target_include_directories(plugin-sdk-cpp INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)

# Enable testing
option(BUILD_TESTING "Build tests" ON)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(tests)
endif()
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ clean: $(examples_clean)
format:
+find ./include -iname *.h -o -iname *.cpp | grep -v "/deps/" | xargs $(CLANG_FORMAT) -i
+find ./examples -iname *.h -o -iname *.cpp | grep -v "/deps/" | xargs $(CLANG_FORMAT) -i
+find ./tests -iname *.h -o -iname *.cpp | grep -v "/deps/" | xargs $(CLANG_FORMAT) -i

.PHONY: deps
deps: $(DEPS_INCLUDEDIR)/plugin_types.h $(DEPS_INCLUDEDIR)/plugin_api.h $(DEPS_INCLUDEDIR)/nlohmann/json.hpp
Expand Down
291 changes: 291 additions & 0 deletions include/falcosecurity/events/codec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#pragma once

#include <cstring>
#include <iomanip>
#include <iostream>
#include <utility>

#include <stdint.h>

namespace codec
{

// Helpers to get member type and class.
template<auto MemberPtr> struct member_type;

template<typename ClassT, typename MemberT, MemberT ClassT::*MemberPtr>
struct member_type<MemberPtr>
{
using type = MemberT;
};

template<auto MemberT>
using member_type_t = typename member_type<MemberT>::type;

template<auto MemberPtr> struct class_type;

template<typename ClassT, typename MemberT, MemberT ClassT::*MemberPtr>
struct class_type<MemberPtr>
{
using type = ClassT;
};

template<auto MemberPtr>
using class_type_t = typename class_type<MemberPtr>::type;

template<auto Ptr> struct is_size_ptr : std::false_type
{
};

template<typename ClassT, size_t ClassT::*Ptr>
struct is_size_ptr<Ptr> : std::true_type
{
};

// Schema

template<auto MemberPtr, auto MemberLenPtr = nullptr> struct Field
{

using type = member_type_t<MemberPtr>;
static constexpr auto member_ptr = MemberPtr;
static constexpr auto member_len_ptr = MemberLenPtr;
static constexpr size_t member_len = sizeof(type);

static_assert(MemberLenPtr == nullptr || is_size_ptr<MemberLenPtr>::value,
"Length member must be size_t");

static_assert(MemberLenPtr == nullptr || std::is_array_v<type> ||
std::is_pointer_v<type>,
"A variable type must be an array or a pointer");
};

template<typename... Fields> struct Schema
{
static constexpr size_t num_fields = sizeof...(Fields);
using fields = std::tuple<Fields...>;
};

// Encoder

template<typename SchemaT> class Encoder
{
public:
using StructT = class_type_t<
std::tuple_element_t<0, typename SchemaT::fields>::member_ptr>;
explicit Encoder(const StructT& obj): m_obj(obj) {}

size_t encode(uint8_t* buf, size_t buf_size)
{
uint8_t* curr_ptr = buf;
size_t available = buf_size;
encode_fields(curr_ptr, available);
return buf_size - available;
}

private:
template<typename Field>
void encode_fixed_field(uint8_t*& buf, size_t& available)
{
using FieldType = typename Field::type;
static_assert(std::is_trivially_copyable_v<FieldType>,
"Fixed fields must be trivially copyable");

if(available >= Field::member_len)
{
const FieldType& src = m_obj.*(Field::member_ptr);
std::memcpy(buf, &src, Field::member_len);
buf += Field::member_len;
available -= Field::member_len;
}
}

template<typename Field>
void encode_variable_field(uint8_t*& buf, size_t& available)
{
using FieldMemberPtrClass = class_type_t<Field::member_ptr>;
using FieldMemberLenPtrClass = class_type_t<Field::member_len_ptr>;
static_assert(
std::is_same_v<FieldMemberPtrClass, FieldMemberLenPtrClass>,
"The len of a variable field must belong to the same struct of "
"the variable field.");

using FieldType = typename Field::type;
const size_t& len = m_obj.*(Field::member_len_ptr);

if(available >= sizeof(size_t) + len)
{
// First we write the len
std::memcpy(buf, &len, sizeof(size_t));
buf += sizeof(size_t);
available -= sizeof(size_t);

// then we write the actual content
const FieldType& src = m_obj.*(Field::member_ptr);
std::memcpy(buf, &src, len);
buf += len;
available -= len;
}
}

template<size_t Is> void encode_field(uint8_t*& buf, size_t& available)
{
using Field = std::tuple_element_t<Is, typename SchemaT::fields>;
if constexpr(Field::member_len_ptr == nullptr)
{
encode_fixed_field<Field>(buf, available);
}
else
{
encode_variable_field<Field>(buf, available);
}
}

template<size_t... Is>
void encode_fields_impl(uint8_t*& buf, size_t& available,
std::index_sequence<Is...>)
{
(encode_field<Is>(buf, available), ...);
}

void encode_fields(uint8_t*& buf, size_t& available)
{
encode_fields_impl(buf, available,
std::make_index_sequence<SchemaT::num_fields>{});
}

// Compile time checks

using SchemaFields = typename SchemaT::fields;
template<size_t I>
using FieldClassT =
class_type_t<std::tuple_element_t<I, SchemaFields>::member_ptr>;

template<size_t... Is>
static constexpr bool check_same_struct(std::index_sequence<Is...>)
{
return (std::is_same_v<StructT, FieldClassT<Is>> && ...);
}

static_assert(
check_same_struct(std::make_index_sequence<SchemaT::num_fields>{}),
"All fields must belong to the same struct");

static_assert(SchemaT::num_fields > 0,
"The schema must define at least a field.");

const StructT& m_obj;
};

template<typename SchemaT> class Decoder
{
public:
using StructT = class_type_t<
std::tuple_element_t<0, typename SchemaT::fields>::member_ptr>;
explicit Decoder(StructT& obj): m_obj(obj) {}

void decode(uint8_t* buf, const size_t& available)
{
uint8_t* cur_ptr = buf;
size_t left = available;

decode_fields(cur_ptr, left);
}

private:
template<typename Field>
void decode_fixed_field(uint8_t*& buf, size_t& left)
{
using FieldType = typename Field::type;
static_assert(std::is_trivially_copyable_v<FieldType>,
"Fixed fields must be trivially copyable");

if(left >= Field::member_len)
{
FieldType& dst = m_obj.*(Field::member_ptr);
std::memcpy(&dst, buf, Field::member_len);
buf += Field::member_len;
left -= Field::member_len;
}
}

template<typename Field>
void decode_variable_field(uint8_t*& buf, size_t& left)
{
using FieldMemberPtrClass = class_type_t<Field::member_ptr>;
using FieldMemberLenPtrClass = class_type_t<Field::member_len_ptr>;
static_assert(
std::is_same_v<FieldMemberPtrClass, FieldMemberLenPtrClass>,
"The len of a variable field must belong to the same struct of "
"the variable field.");

size_t& len = m_obj.*(Field::member_len_ptr);

if(left >= sizeof(size_t))
{
std::memcpy(&len, buf, sizeof(size_t));
buf += sizeof(size_t);
left -= sizeof(size_t);

if(left >= len)
{

using FieldType = typename Field::type;
FieldType& dst = m_obj.*(Field::member_ptr);
std::memcpy(&dst, buf, len);
buf += len;
left -= len;
}
}
}

template<size_t Is> void decode_field(uint8_t*& buf, size_t& left)
{
using Field = std::tuple_element_t<Is, typename SchemaT::fields>;
if constexpr(Field::member_len_ptr == nullptr)
{
decode_fixed_field<Field>(buf, left);
}
else
{
decode_variable_field<Field>(buf, left);
}
}

// Compile time checks

template<size_t... Is>
void decode_fields_impl(uint8_t*& buf, size_t& left,
std::index_sequence<Is...>)
{
(decode_field<Is>(buf, left), ...);
}

void decode_fields(uint8_t*& buf, size_t& left)
{
decode_fields_impl(buf, left,
std::make_index_sequence<SchemaT::num_fields>{});
}

using SchemaFields = typename SchemaT::fields;
template<size_t I>
using FieldClassT =
class_type_t<std::tuple_element_t<I, SchemaFields>::member_ptr>;

template<size_t... Is>
static constexpr bool check_same_struct(std::index_sequence<Is...>)
{
return (std::is_same_v<StructT, FieldClassT<Is>> && ...);
}

static_assert(
check_same_struct(std::make_index_sequence<SchemaT::num_fields>{}),
"All fields must belong to the same struct");

static_assert(SchemaT::num_fields > 0,
"The schema must define at least a field.");

StructT& m_obj;
};

} // namespace codec
41 changes: 41 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (C) 2025 The Falco Authors.
#
# 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(FetchContent)

# Fetch Google Test
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

# Include Google Test's CMake functions
include(GoogleTest)

# Codec test
add_executable(codec_test codec_test.cpp)
target_link_libraries(codec_test PRIVATE
plugin-sdk-cpp
GTest::gtest_main
)

# Discover tests
gtest_discover_tests(codec_test)
Loading