// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2023 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 <inttypes.h>
#include <string.h>
#include <memory>
#include <vector>
#include <numeric>
#include <json/json.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#include <valijson/adapters/jsoncpp_adapter.hpp>
#pragma GCC diagnostic pop
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validator.hpp>

#include <libsinsp/sinsp_int.h>
#include <libsinsp/sinsp_exception.h>
#include <libsinsp/plugin.h>
#include <libsinsp/plugin_filtercheck.h>
#include <libscap/strl.h>

static constexpr const char* s_not_init_err = "plugin capability used before init";

static constexpr const char* s_init_twice_err = "plugin has been initialized twice";

//
// Plugin Type Look Up Table
//
const std::unordered_map<std::string, ppm_param_type> s_pt_lut = {
        {"string", PT_CHARBUF},
        {"uint64", PT_UINT64},
        {"reltime", PT_RELTIME},
        {"abstime", PT_ABSTIME},
        {"bool", PT_BOOL},
        {"ipaddr", PT_IPADDR},
        {"ipnet", PT_IPNET},
};

// Used below--set a std::string from the provided allocated charbuf
static std::string str_from_alloc_charbuf(const char* charbuf) {
	std::string str;

	if(charbuf != NULL) {
		str = charbuf;
	}

	return str;
}

const char* sinsp_plugin::get_owner_last_error(ss_plugin_owner_t* o) {
	auto t = static_cast<sinsp_plugin*>(o);
	if(t->m_last_owner_err.empty()) {
		return NULL;
	}
	return t->m_last_owner_err.c_str();
}

static void plugin_log_fn(ss_plugin_owner_t* o,
                          const char* component,
                          const char* msg,
                          ss_plugin_log_severity sev) {
	auto t = static_cast<sinsp_plugin*>(o);
	std::string prefix = (component == NULL) ? t->name() : std::string(component);
	libsinsp_logger()->log(prefix + ": " + msg, (sinsp_logger::severity)sev);
}

std::shared_ptr<sinsp_plugin> sinsp_plugin::create(
        const plugin_api* api,
        const std::shared_ptr<libsinsp::state::table_registry>& treg,
        const std::shared_ptr<sinsp_thread_pool>& tpool,
        std::string& errstr) {
	char loadererr[PLUGIN_MAX_ERRLEN];
	auto handle = plugin_load_api(api, loadererr);
	if(handle == NULL) {
		errstr = loadererr;
		return nullptr;
	}

	auto plugin = std::make_shared<sinsp_plugin>(handle, treg, tpool);
	if(!plugin->resolve_dylib_symbols(errstr)) {
		// plugin and handle get deleted here by shared_ptr
		return nullptr;
	}

	return plugin;
}

std::shared_ptr<sinsp_plugin> sinsp_plugin::create(
        const std::string& filepath,
        const std::shared_ptr<libsinsp::state::table_registry>& treg,
        const std::shared_ptr<sinsp_thread_pool>& tpool,
        std::string& errstr) {
	char loadererr[PLUGIN_MAX_ERRLEN];
	auto handle = plugin_load(filepath.c_str(), loadererr);
	if(handle == NULL) {
		errstr = loadererr;
		return nullptr;
	}

	auto plugin = std::make_shared<sinsp_plugin>(handle, treg, tpool);
	if(!plugin->resolve_dylib_symbols(errstr)) {
		// plugin and handle get deleted here by shared_ptr
		return nullptr;
	}

	return plugin;
}

bool sinsp_plugin::is_plugin_loaded(const std::string& filepath) {
	return plugin_is_loaded(filepath.c_str());
}

sinsp_plugin::~sinsp_plugin() {
	destroy();
	plugin_unload(m_handle);

	auto cur_async_handler = m_async_evt_handler.load();
	if(cur_async_handler) {
		m_async_evt_handler.store(nullptr);
		delete cur_async_handler;
	}
}

bool sinsp_plugin::init(const std::string& config, std::string& errstr) {
	if(m_inited) {
		errstr = std::string(s_init_twice_err) + ": " + m_name;
		return false;
	}

	if(!m_handle->api.init) {
		errstr = std::string("init api symbol not found");
		return false;
	}

	ss_plugin_rc rc;

	std::string conf = config;
	validate_config(conf);

	ss_plugin_init_input in = {};
	in.owner = this;
	in.get_owner_last_error = sinsp_plugin::get_owner_last_error;
	in.tables = NULL;
	in.config = conf.c_str();
	in.log_fn = &plugin_log_fn;

	ss_plugin_init_tables_input tables_in = {};
	ss_plugin_table_fields_vtable_ext table_fields_ext = {};
	ss_plugin_table_reader_vtable reader_deprecated = {};  // unused
	ss_plugin_table_reader_vtable_ext table_reader_ext = {};
	ss_plugin_table_writer_vtable writer_deprecated = {};  // unused
	ss_plugin_table_writer_vtable_ext table_writer_ext = {};

	if(m_caps & (CAP_PARSING | CAP_EXTRACTION)) {
		tables_in.fields_ext = &table_fields_ext;
		tables_in.reader_ext = &table_reader_ext;
		tables_in.writer_ext = &table_writer_ext;
		sinsp_plugin::table_field_api(tables_in.fields, table_fields_ext);
		sinsp_plugin::table_read_api(reader_deprecated, table_reader_ext);
		sinsp_plugin::table_write_api(writer_deprecated, table_writer_ext);
		tables_in.list_tables = sinsp_plugin::table_api_list_tables;
		tables_in.get_table = sinsp_plugin::table_api_get_table;
		tables_in.add_table = sinsp_plugin::table_api_add_table;
		in.tables = &tables_in;
	}
	ss_plugin_t* state = m_handle->api.init(&in, &rc);
	if(state != NULL) {
		// Plugins can return a state even if the result code is
		// SS_PLUGIN_FAILURE, which can be useful to set an init
		// error that can later be retrieved through get_last_error().
		m_state = state;
	}

	m_inited = true;
	if(rc != SS_PLUGIN_SUCCESS) {
		errstr = "could not initialize plugin: " + get_last_error();
		return false;
	}

	// resolve post-init event code filters
	if(m_caps & CAP_EXTRACTION) {
		/* Here we populate the `m_extract_event_codes` for the plugin, while
		 * `m_extract_event_sources` is already populated in the plugin_init */
		resolve_dylib_compatible_codes(m_handle->api.get_extract_event_types,
		                               m_extract_event_sources,
		                               m_extract_event_codes);
	}
	if(m_caps & CAP_PARSING) {
		/* Here we populate the `m_parse_event_codes` for the plugin, while `m_parse_event_sources`
		 * is already populated in the plugin_init */
		resolve_dylib_compatible_codes(m_handle->api.get_parse_event_types,
		                               m_parse_event_sources,
		                               m_parse_event_codes);
	}

	// do some defensive garbage collection
	clear_ephemeral_tables();
	clear_accessed_entries();
	clear_created_entries();

	return true;
}

void sinsp_plugin::destroy() {
	m_inited = false;
	if(m_state && m_handle->api.destroy) {
		m_handle->api.destroy(m_state);
		m_state = NULL;
	}
}

std::string sinsp_plugin::get_last_error() const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	std::string ret;
	if(m_state) {
		ret = str_from_alloc_charbuf(m_handle->api.get_last_error(m_state));
	} else {
		ret = "plugin handle or 'get_last_error' function not defined";
	}

	return ret;
}

void sinsp_plugin::resolve_dylib_field_arg(Json::Value root, filtercheck_field_info& tf) {
	if(root.isNull()) {
		return;
	}

	const Json::Value& isRequired = root.get("isRequired", Json::Value::null);
	if(!isRequired.isNull()) {
		if(!isRequired.isBool()) {
			throw sinsp_exception(std::string("error in plugin ") + m_name + ": field " +
			                      tf.m_name + " isRequired property is not boolean");
		}

		if(isRequired.asBool() == true) {
			// All the extra casting is because this is the one flags value
			// that is strongly typed and not just an int.
			tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
			                                       (int)filtercheck_field_flags::EPF_ARG_REQUIRED);
		}
	}

	const Json::Value& isIndex = root.get("isIndex", Json::Value::null);
	if(!isIndex.isNull()) {
		if(!isIndex.isBool()) {
			throw sinsp_exception(std::string("error in plugin ") + m_name + ": field " +
			                      tf.m_name + " isIndex property is not boolean");
		}

		if(isIndex.asBool() == true) {
			// We set `EPF_ARG_ALLOWED` implicitly.
			tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
			                                       (int)filtercheck_field_flags::EPF_ARG_INDEX);
			tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
			                                       (int)filtercheck_field_flags::EPF_ARG_ALLOWED);
		}
	}

	const Json::Value& isKey = root.get("isKey", Json::Value::null);
	if(!isKey.isNull()) {
		if(!isKey.isBool()) {
			throw sinsp_exception(std::string("error in plugin ") + m_name + ": field " +
			                      tf.m_name + " isKey property is not boolean");
		}

		if(isKey.asBool() == true) {
			// We set `EPF_ARG_ALLOWED` implicitly.
			tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
			                                       (int)filtercheck_field_flags::EPF_ARG_KEY);
			tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
			                                       (int)filtercheck_field_flags::EPF_ARG_ALLOWED);
		}
	}

	if((tf.m_flags & filtercheck_field_flags::EPF_ARG_REQUIRED) &&
	   !(tf.m_flags & filtercheck_field_flags::EPF_ARG_INDEX ||
	     tf.m_flags & filtercheck_field_flags::EPF_ARG_KEY)) {
		throw sinsp_exception(std::string("error in plugin ") + m_name + ": field " + tf.m_name +
		                      " arg has isRequired true, but none of isKey nor isIndex is true");
	}
	return;
}

// this logic is shared between the field extraction and event parsing caps
void sinsp_plugin::resolve_dylib_compatible_codes(uint16_t* (*get_codes)(uint32_t*, ss_plugin_t*),
                                                  const std::unordered_set<std::string>& sources,
                                                  libsinsp::events::set<ppm_event_code>& codes) {
	codes.clear();
	if(get_codes != NULL) {
		uint32_t ntypes = 0;
		auto types = get_codes(&ntypes, m_state);
		if(types) {
			for(uint32_t i = 0; i < ntypes; i++) {
				codes.insert((ppm_event_code)types[i]);
			}
		}
	}
	if(codes.empty()) {
		if(is_source_compatible(sources, sinsp_syscall_event_source_name)) {
			codes = libsinsp::events::all_event_set();
		} else {
			codes.insert(ppm_event_code::PPME_PLUGINEVENT_E);
		}
	}
}

static void resolve_dylib_json_strlist(const std::string& plname,
                                       const std::string& symbol,
                                       const char* (*get_list)(),
                                       std::unordered_set<std::string>& out,
                                       bool allow_empty) {
	out.clear();
	if(get_list == NULL) {
		return;
	}

	std::string jsonstr = str_from_alloc_charbuf(get_list());

	if(jsonstr.empty()) {
		if(allow_empty) {
			// Do nothing, we allow an empty json string.
			return;
		} else {
			throw sinsp_exception("error in plugin " + plname + ": '" + symbol +
			                      "' did not return a json array but it should");
		}
	}

	Json::Value root;
	if(!Json::Reader().parse(jsonstr, root) || root.type() != Json::arrayValue) {
		throw sinsp_exception("error in plugin " + plname + ": '" + symbol +
		                      "' did not return a json array");
	}
	for(const auto& j : root) {
		if(!j.isConvertibleTo(Json::stringValue)) {
			throw sinsp_exception("error in plugin " + plname + ": '" + symbol +
			                      "' did not return a json array");
		}
		auto src = j.asString();
		if(!src.empty()) {
			out.insert(src);
		}
	}
}

// this logic is shared between the field extraction and event parsing caps
void sinsp_plugin::resolve_dylib_compatible_sources(const std::string& symbol,
                                                    const char* (*get_sources)(),
                                                    std::unordered_set<std::string>& sources) {
	resolve_dylib_json_strlist(name(), symbol, get_sources, sources, true);

	// A plugin with source capability extracts/parses events
	// from its own specific source (if no other sources are specified)
	if(m_caps & CAP_SOURCING && !m_event_source.empty()) {
		sources.insert(m_event_source);
	}
}

bool sinsp_plugin::resolve_dylib_symbols(std::string& errstr) {
	char err[PLUGIN_MAX_ERRLEN];

	// Before doing anything else, check the required api version
	if(!plugin_check_required_api_version(m_handle, err)) {
		errstr = err;
		return false;
	}

	// check that the API requirements are satisfied
	// These are the minimum APIs that all plugins should implement
	if(!plugin_check_required_symbols(m_handle, err)) {
		errstr = err;
		return false;
	}

	// store descriptive info in internal state
	m_name = str_from_alloc_charbuf(m_handle->api.get_name());
	m_description = str_from_alloc_charbuf(m_handle->api.get_description());
	m_contact = str_from_alloc_charbuf(m_handle->api.get_contact());
	std::string version_str = str_from_alloc_charbuf(m_handle->api.get_version());
	m_plugin_version = sinsp_version(version_str);
	if(!m_plugin_version.is_valid()) {
		errstr = "plugin provided an invalid version string: '" + version_str + "'";
		return false;
	}
	std::string req_api_version_str =
	        str_from_alloc_charbuf(m_handle->api.get_required_api_version());
	m_required_api_version = sinsp_version(req_api_version_str);
	if(!m_required_api_version.is_valid()) {
		errstr = "plugin provided an invalid required api version string: '" + req_api_version_str +
		         "'";
		return false;
	}

	// read capabilities and process their info
	m_caps = plugin_get_capabilities(m_handle, err);
	if(m_caps & CAP_BROKEN) {
		errstr = "broken plugin capabilities: " + std::string(err);
		return false;
	}
	if(m_caps == CAP_NONE) {
		errstr = "plugin does not implement any capability";
		return false;
	}

	if(m_caps & CAP_SOURCING) {
		/* Default case: no id and no source */
		m_id = 0;
		m_event_source.clear();
		if(m_handle->api.get_id != NULL && m_handle->api.get_event_source != NULL &&
		   m_handle->api.get_id() != 0) {
			m_id = m_handle->api.get_id();
			m_event_source = str_from_alloc_charbuf(m_handle->api.get_event_source());
			if(m_event_source == sinsp_syscall_event_source_name) {
				errstr =
				        "plugin can't implement the reserved event source '" + m_event_source + "'";
				return false;
			}
		}
	}

	if(m_caps & CAP_EXTRACTION) {
		//
		// If filter fields are exported by the plugin, get the json from get_fields(),
		// parse it, create our list of fields, and create a filtercheck from the fields.
		//
		const char* sfields = m_handle->api.get_fields();
		if(sfields == NULL) {
			throw sinsp_exception(std::string("error in plugin ") + name() +
			                      ": get_fields returned a null string");
		}
		std::string json(sfields);

		Json::Value root;
		if(Json::Reader().parse(json, root) == false || root.type() != Json::arrayValue) {
			throw sinsp_exception(std::string("error in plugin ") + name() +
			                      ": get_fields returned an invalid JSON");
		}

		m_fields.clear();
		for(Json::Value::ArrayIndex j = 0; j < root.size(); j++) {
			filtercheck_field_info tf;

			// plugin fields can't ever be trusted for pointer stability
			tf.m_flags = EPF_NO_PTR_STABILITY;

			const Json::Value& jvtype = root[j]["type"];
			std::string ftype = jvtype.asString();
			if(ftype == "") {
				throw sinsp_exception(std::string("error in plugin ") + name() +
				                      ": field JSON entry has no type");
			}
			const Json::Value& jvname = root[j]["name"];
			std::string fname = jvname.asString();
			if(fname == "") {
				throw sinsp_exception(std::string("error in plugin ") + name() +
				                      ": field JSON entry has no name");
			}
			const Json::Value& jvdisplay = root[j]["display"];
			std::string fdisplay = jvdisplay.asString();
			const Json::Value& jvdesc = root[j]["desc"];
			std::string fdesc = jvdesc.asString();
			if(fdesc == "") {
				throw sinsp_exception(std::string("error in plugin ") + name() +
				                      ": field JSON entry has no desc");
			}

			tf.m_name = fname;
			tf.m_display = fdisplay;
			tf.m_description = fdesc;
			tf.m_print_format = PF_DEC;
			if(s_pt_lut.find(ftype) != s_pt_lut.end()) {
				tf.m_type = s_pt_lut.at(ftype);
			} else {
				throw sinsp_exception(std::string("error in plugin ") + name() +
				                      ": invalid field type " + ftype);
			}

			const Json::Value& jvIsList = root[j].get("isList", Json::Value::null);
			if(!jvIsList.isNull()) {
				if(!jvIsList.isBool()) {
					throw sinsp_exception(std::string("error in plugin ") + name() + ": field " +
					                      fname + " isList property is not boolean ");
				}

				if(jvIsList.asBool()) {
					tf.m_flags =
					        (filtercheck_field_flags)((int)tf.m_flags |
					                                  (int)filtercheck_field_flags::EPF_IS_LIST);
				}
			}

			const Json::Value& jvoutput = root[j].get("addOutput", Json::Value::null);
			if(!jvoutput.isNull()) {
				if(!jvoutput.isBool()) {
					throw sinsp_exception(std::string("error in plugin ") + name() + ": field " +
					                      fname + " addOutput property is not boolean ");
				}

				if(jvoutput.asBool()) {
					tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
					                                       (int)filtercheck_field_flags::
					                                               EPF_FORMAT_SUGGESTED);
				}
			}

			resolve_dylib_field_arg(root[j].get("arg", Json::Value::null), tf);

			const Json::Value& jvProperties = root[j].get("properties", Json::Value::null);
			if(!jvProperties.isNull()) {
				if(!jvProperties.isArray()) {
					throw sinsp_exception(std::string("error in plugin ") + name() + ": field " +
					                      fname + " properties property is not array ");
				}

				for(const auto& prop : jvProperties) {
					if(!prop.isString()) {
						throw sinsp_exception(std::string("error in plugin ") + name() +
						                      ": field " + fname +
						                      " properties value is not string ");
					}

					const std::string& str = prop.asString();

					// "hidden" is used inside and outside libs. "info" and "conversation" are used
					// outside libs.
					if(str == "hidden") {
						tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
						                                       (int)filtercheck_field_flags::
						                                               EPF_TABLE_ONLY);
					} else if(str == "info") {
						tf.m_flags =
						        (filtercheck_field_flags)((int)tf.m_flags |
						                                  (int)filtercheck_field_flags::EPF_INFO);
					} else if(str == "conversation") {
						tf.m_flags = (filtercheck_field_flags)((int)tf.m_flags |
						                                       (int)filtercheck_field_flags::
						                                               EPF_CONVERSATION);
					}
				}
			}
			m_fields.push_back(tf);
		}
		auto fields_size = m_fields.size();
		if(fields_size == 0) {
			throw sinsp_exception(
			        std::string("error in plugin ") + name() +
			        ": plugins with extraction capability must export at least one field");
		}

		// populate fields info
		m_fields_info.m_name = name() + std::string(" (plugin)");
		m_fields_info.m_fields = &m_fields[0];
		m_fields_info.m_nfields = fields_size;
		m_fields_info.m_flags = filter_check_info::FL_NONE;

		// This API is not compulsory for the extraction capability
		resolve_dylib_compatible_sources("get_extract_event_sources",
		                                 m_handle->api.get_extract_event_sources,
		                                 m_extract_event_sources);
	}

	if(m_caps & CAP_PARSING) {
		resolve_dylib_compatible_sources("get_parse_event_sources",
		                                 m_handle->api.get_parse_event_sources,
		                                 m_parse_event_sources);
	}

	if(m_caps & CAP_ASYNC) {
		resolve_dylib_compatible_sources("get_async_event_sources",
		                                 m_handle->api.get_async_event_sources,
		                                 m_async_event_sources);
		resolve_dylib_json_strlist(name(),
		                           "get_async_events",
		                           m_handle->api.get_async_events,
		                           m_async_event_names,
		                           false);

		// Register each async event name in sinsp global event table.
		for(const auto& name : m_async_event_names) {
			if(g_infotables.m_event_size == MAX_EVENTINFO_SIZE) {
				// too many async events registered
				break;
			}
			g_infotables.m_event_info[g_infotables.m_event_size] =
			        g_infotables.m_event_info[PPME_ASYNCEVENT_E];
			strlcpy(g_infotables.m_event_info[g_infotables.m_event_size].name,
			        name.c_str(),
			        PPM_MAX_NAME_LEN);
			g_infotables.m_event_size++;
		}
	}

	return true;
}

bool sinsp_plugin::check_required_schema_version(sinsp_version event_schema_version,
                                                 std::string& err) {
	// Check if the plugin consumes driver events and needs schema version validation
	bool needs_schema_check = false;

	// Check if plugin provides parsing capabilities and consumes syscall events
	if(m_caps & CAP_PARSING) {
		for(const auto& event_type : m_parse_event_codes) {
			if(event_type != ppm_event_code::PPME_PLUGINEVENT_E) {
				needs_schema_check = true;
				break;
			}
		}
	}

	// Check if plugin provides field extraction capabilities and consumes syscall events
	if(!needs_schema_check && (m_caps & CAP_EXTRACTION)) {
		for(const auto& event_type : m_extract_event_codes) {
			if(event_type != ppm_event_code::PPME_PLUGINEVENT_E) {
				needs_schema_check = true;
				break;
			}
		}
	}

	// If schema check is needed, validate the schema version
	if(needs_schema_check) {
		// Default to schema version 3.0.0, which is the major schema version of the release
		// predating the introduction of the event schema version check
		uint32_t major = 3, minor = 0, patch = 0;
		const char* required_version = NULL;
		if(m_handle->api.get_required_event_schema_version != NULL) {
			required_version = m_handle->api.get_required_event_schema_version(m_state);
			if(required_version != NULL) {
				if(sscanf(required_version,
				          "%" PRIu32 ".%" PRIu32 ".%" PRIu32,
				          &major,
				          &minor,
				          &patch) != 3) {
					err = "plugin " + name() +
					      " provided an invalid required event schema version: '" +
					      std::string(required_version) + "'";
					return false;
				}
			}
		}

		std::string failmsg("");
		/* The plugin requires a minimum schema version */
		if(event_schema_version.major() != major) {
			failmsg = "major versions disagree";
		} else if(event_schema_version.minor() < minor) {
			failmsg = "minor version is less than the requested one";
		} else if(event_schema_version.minor() == minor && event_schema_version.patch() < patch) {
			failmsg = "patch version is less than the requested one";
		}

		if(!failmsg.empty()) {
			err = "plugin " + name() + " required event schema version '" +
			      (required_version ? std::string(required_version) : "3.0.0") +
			      "' not compatible with the event schema version in use '" +
			      event_schema_version.as_string() + "': " + failmsg;
			return false;
		}
	}

	// No schema check needed
	return true;
}

std::string sinsp_plugin::get_init_schema(ss_plugin_schema_type& schema_type) const {
	schema_type = SS_PLUGIN_SCHEMA_NONE;
	if(m_handle->api.get_init_schema != NULL) {
		return str_from_alloc_charbuf(m_handle->api.get_init_schema(&schema_type));
	}
	return std::string("");
}

const libsinsp::events::set<ppm_event_code>& sinsp_plugin::extract_event_codes() const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}
	return m_extract_event_codes;
}

const libsinsp::events::set<ppm_event_code>& sinsp_plugin::parse_event_codes() const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}
	return m_parse_event_codes;
}

void sinsp_plugin::validate_config(std::string& config) {
	ss_plugin_schema_type schema_type;
	std::string schema = get_init_schema(schema_type);
	if(!schema.empty() && schema_type != SS_PLUGIN_SCHEMA_NONE) {
		switch(schema_type) {
		case SS_PLUGIN_SCHEMA_JSON:
			validate_config_json_schema(config, schema);
			break;
		default:
			ASSERT(false);
			throw sinsp_exception(std::string("error in plugin ") + name() +
			                      ": get_init_schema returned an unknown schema type " +
			                      std::to_string(schema_type));
		}
	}
}

void sinsp_plugin::validate_config_json_schema(std::string& config, std::string& schema) {
	Json::Value schemaJson;
	if(!Json::Reader().parse(schema, schemaJson) || schemaJson.type() != Json::objectValue) {
		throw sinsp_exception(std::string("error in plugin ") + name() +
		                      ": get_init_schema did not return a json object");
	}

	// stub empty configs to an empty json object
	if(config.size() == 0) {
		config = "{}";
	}
	Json::Value configJson;
	if(!Json::Reader().parse(config, configJson)) {
		throw sinsp_exception(std::string("error in plugin ") + name() +
		                      ": init config is not a valid json");
	}

	// validate config with json schema
	valijson::Schema schemaDef;
	valijson::SchemaParser schemaParser;
	valijson::Validator validator;
	valijson::ValidationResults validationResults;
	valijson::adapters::JsonCppAdapter configAdapter(configJson);
	valijson::adapters::JsonCppAdapter schemaAdapter(schemaJson);
	schemaParser.populateSchema(schemaAdapter, schemaDef);
	if(!validator.validate(schemaDef, configAdapter, &validationResults)) {
		valijson::ValidationResults::Error error;
		// report only the top-most error
		if(validationResults.popError(error)) {
			throw sinsp_exception(
			        std::string("error in plugin ") + name() + " init config: In " +
			        std::accumulate(error.context.begin(), error.context.end(), std::string("")) +
			        ", " + error.description);
		}
		// validation failed with no specific error
		throw sinsp_exception(std::string("error in plugin ") + name() +
		                      " init config: failed parsing with provided schema");
	}
}

bool sinsp_plugin::set_config(const std::string& config) {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	std::string conf = config;
	validate_config(conf);

	ss_plugin_set_config_input input;
	input.config = conf.c_str();

	if(!m_handle->api.set_config) {
		return false;
	}

	return m_handle->api.set_config(m_state, &input) == SS_PLUGIN_SUCCESS;
}

static void set_plugin_metric_value(metrics_v2& metric,
                                    metrics_v2_value_type type,
                                    ss_plugin_metric_value val) {
	switch(type) {
	case METRIC_VALUE_TYPE_U32:
		metric.value.u32 = val.u32;
		break;
	case METRIC_VALUE_TYPE_S32:
		metric.value.s32 = val.s32;
		break;
	case METRIC_VALUE_TYPE_U64:
		metric.value.u64 = val.u64;
		break;
	case METRIC_VALUE_TYPE_S64:
		metric.value.s64 = val.s64;
		break;
	case METRIC_VALUE_TYPE_D:
		metric.value.d = val.d;
		break;
	case METRIC_VALUE_TYPE_F:
		metric.value.f = val.f;
		break;
	case METRIC_VALUE_TYPE_I:
		metric.value.i = val.i;
		break;
	default:
		break;
	}
}

std::vector<metrics_v2> sinsp_plugin::get_metrics() const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	std::vector<metrics_v2> metrics;
	uint32_t num_metrics = 0;

	if(!m_handle->api.get_metrics) {
		return metrics;
	}

	ss_plugin_metric* plugin_metrics = m_handle->api.get_metrics(m_state, &num_metrics);
	for(uint32_t i = 0; i < num_metrics; i++) {
		ss_plugin_metric* plugin_metric = plugin_metrics + i;

		metrics_v2 metric;

		// copy plugin name
		snprintf(metric.name, METRIC_NAME_MAX, "%s.%s", m_name.c_str(), plugin_metric->name);

		metric.flags = METRICS_V2_PLUGINS;
		metric.unit = METRIC_VALUE_UNIT_COUNT;
		metric.type = static_cast<metrics_v2_value_type>(plugin_metric->value_type);
		metric.metric_type = static_cast<metrics_v2_metric_type>(plugin_metric->type);
		set_plugin_metric_value(metric, metric.type, plugin_metric->value);

		metrics.emplace_back(metric);
	}

	return metrics;
}

sinsp_thread_pool::routine_id_t sinsp_plugin::subscribe_routine(
        ss_plugin_routine_fn_t routine_fn,
        ss_plugin_routine_state_t* routine_state) {
	if(!m_thread_pool) {
		return reinterpret_cast<sinsp_thread_pool::routine_id_t>(nullptr);
	}

	auto f = [this, routine_fn, routine_state]() -> bool {
		return static_cast<bool>(routine_fn(m_state, routine_state));
	};

	return m_thread_pool->subscribe(f);
}

bool sinsp_plugin::unsubscribe_routine(sinsp_thread_pool::routine_id_t routine_id) {
	if(!m_thread_pool || !routine_id) {
		return false;
	}

	return m_thread_pool->unsubscribe(routine_id);
}

ss_plugin_routine_t* plugin_subscribe_routine(ss_plugin_owner_t* o,
                                              ss_plugin_routine_fn_t r,
                                              ss_plugin_routine_state_t* s) {
	auto t = static_cast<sinsp_plugin*>(o);
	auto res = t->subscribe_routine(r, s);

	return reinterpret_cast<ss_plugin_routine_t*>(res);
}

ss_plugin_rc plugin_unsubscribe_routine(ss_plugin_owner_t* o, ss_plugin_routine_t* r) {
	auto t = static_cast<sinsp_plugin*>(o);
	auto id = reinterpret_cast<sinsp_thread_pool::routine_id_t>(r);

	return t->unsubscribe_routine(id) ? SS_PLUGIN_SUCCESS : SS_PLUGIN_FAILURE;
}

bool sinsp_plugin::capture_open() {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	if(!m_handle->api.capture_open) {
		return false;
	}

	ss_plugin_routine_vtable routine_vtable;
	routine_vtable.subscribe = &plugin_subscribe_routine;
	routine_vtable.unsubscribe = &plugin_unsubscribe_routine;

	ss_plugin_capture_listen_input in;
	ss_plugin_table_reader_vtable_ext table_reader_ext;
	ss_plugin_table_writer_vtable_ext table_writer_ext;
	ss_plugin_table_reader_vtable table_reader;
	ss_plugin_table_writer_vtable table_writer;

	in.owner = (ss_plugin_owner_t*)this;
	in.get_owner_last_error = sinsp_plugin::get_owner_last_error;
	in.table_reader_ext = &table_reader_ext;
	in.table_writer_ext = &table_writer_ext;
	in.routine = &routine_vtable;

	sinsp_plugin::table_read_api(table_reader, table_reader_ext);
	sinsp_plugin::table_write_api(table_writer, table_writer_ext);

	return m_handle->api.capture_open(m_state, &in) == SS_PLUGIN_SUCCESS;
}

bool sinsp_plugin::capture_close() {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	if(!m_handle->api.capture_close) {
		return false;
	}

	ss_plugin_routine_vtable routine_vtable;
	routine_vtable.subscribe = &plugin_subscribe_routine;
	routine_vtable.unsubscribe = &plugin_unsubscribe_routine;

	ss_plugin_capture_listen_input in;
	ss_plugin_table_reader_vtable_ext table_reader_ext;
	ss_plugin_table_writer_vtable_ext table_writer_ext;
	ss_plugin_table_reader_vtable table_reader;
	ss_plugin_table_writer_vtable table_writer;

	in.owner = (ss_plugin_owner_t*)this;
	in.get_owner_last_error = sinsp_plugin::get_owner_last_error;
	in.table_reader_ext = &table_reader_ext;
	in.table_writer_ext = &table_writer_ext;
	in.routine = &routine_vtable;

	sinsp_plugin::table_read_api(table_reader, table_reader_ext);
	sinsp_plugin::table_write_api(table_writer, table_writer_ext);

	return m_handle->api.capture_close(m_state, &in) == SS_PLUGIN_SUCCESS;
}

/** Event Source CAP **/

scap_source_plugin& sinsp_plugin::as_scap_source() {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	if(!(caps() & CAP_SOURCING)) {
		throw sinsp_exception(
		        "can't create scap_source_plugin from a plugin without CAP_SOURCING capability.");
	}

	m_scap_source_plugin.state = m_state;
	m_scap_source_plugin.name = m_name.c_str();
	m_scap_source_plugin.id = m_id;
	m_scap_source_plugin.open = m_handle->api.open;
	m_scap_source_plugin.close = m_handle->api.close;
	m_scap_source_plugin.get_last_error = m_handle->api.get_last_error;
	m_scap_source_plugin.next_batch = m_handle->api.next_batch;
	return m_scap_source_plugin;
}

std::string sinsp_plugin::get_progress(uint32_t& progress_pct) const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	std::string ret;
	progress_pct = 0;

	if(!m_handle->api.get_progress || !m_scap_source_plugin.handle) {
		return ret;
	}

	uint32_t ppct;
	ret = str_from_alloc_charbuf(
	        m_handle->api.get_progress(m_state, m_scap_source_plugin.handle, &ppct));

	progress_pct = ppct;

	return ret;
}

std::string sinsp_plugin::event_to_string(sinsp_evt* evt) const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	if(evt->get_type() != PPME_PLUGINEVENT_E || evt->get_param(0)->as<uint32_t>() != m_id) {
		throw sinsp_exception("can't format unknown non-plugin event to string");
	}

	std::string ret = "";
	const auto data_param = evt->get_param(1);
	const auto data = data_param->data();
	const auto data_len = data_param->len();
	if(m_state && m_handle->api.event_to_string) {
		ss_plugin_event_input input;
		input.evt = (const ss_plugin_event*)evt->get_scap_evt();
		input.evtnum = evt->get_num();
		input.evtsrc = evt->get_source_name();
		ret = str_from_alloc_charbuf(m_handle->api.event_to_string(m_state, &input));
	}
	if(ret.empty()) {
		ret += "datalen=";
		ret += std::to_string(data_len);
		ret += " data=";
		for(size_t i = 0; i < std::min(data_len, uint32_t(50)); ++i) {
			if(!std::isprint(data[i])) {
				ret += "<binary>";
				return ret;
			}
		}
		ret.append((char*)data, std::min(data_len, uint32_t(50)));
		if(data_len > 50) {
			ret += "...";
		}
	}
	return ret;
}

std::vector<sinsp_plugin::open_param> sinsp_plugin::list_open_params() const {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	std::vector<sinsp_plugin::open_param> list;
	if(m_state && m_handle->api.list_open_params) {
		ss_plugin_rc rc;
		std::string jsonString =
		        str_from_alloc_charbuf(m_handle->api.list_open_params(m_state, &rc));
		if(rc != SS_PLUGIN_SUCCESS) {
			throw sinsp_exception(std::string("error in plugin ") + name() +
			                      ": list_open_params has error " + get_last_error());
		}

		if(jsonString.size() > 0) {
			Json::Value root;
			if(Json::Reader().parse(jsonString, root) == false || root.type() != Json::arrayValue) {
				throw sinsp_exception(std::string("error in plugin ") + name() +
				                      ": list_open_params returned a non-array JSON");
			}
			for(Json::Value::ArrayIndex i = 0; i < root.size(); i++) {
				open_param param;
				param.value = root[i]["value"].asString();
				if(param.value == "") {
					throw sinsp_exception(std::string("error in plugin ") + name() +
					                      ": list_open_params has entry with no value");
				}
				param.desc = root[i]["desc"].asString();
				param.separator = root[i]["separator"].asString();
				list.push_back(param);
			}
		}
	}

	return list;
}

/** End of Event Source CAP **/

/** Field Extraction CAP **/

std::unique_ptr<sinsp_filter_check> sinsp_plugin::new_filtercheck(
        const std::shared_ptr<sinsp_plugin>& plugin) {
	return std::make_unique<sinsp_filter_check_plugin>(plugin);
}

bool sinsp_plugin::extract_fields_and_offsets(sinsp_evt* evt,
                                              uint32_t num_fields,
                                              ss_plugin_extract_field* fields,
                                              ss_plugin_extract_value_offsets* value_offsets) {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	ss_plugin_event_input ev;
	ev.evt = (const ss_plugin_event*)evt->get_scap_evt();
	ev.evtnum = evt->get_num();
	ev.evtsrc = evt->get_source_name();

	ss_plugin_field_extract_input in;
	ss_plugin_table_reader_vtable_ext table_reader_ext;
	in.num_fields = num_fields;
	in.fields = fields;
	in.owner = (ss_plugin_owner_t*)this;
	in.get_owner_last_error = sinsp_plugin::get_owner_last_error;
	in.table_reader_ext = &table_reader_ext;
	in.value_offsets = value_offsets;
	sinsp_plugin::table_read_api(in.table_reader, table_reader_ext);
	auto res = m_handle->api.extract_fields(m_state, &ev, &in) == SS_PLUGIN_SUCCESS;

	// do some defensive garbage collection
	clear_ephemeral_tables();
	clear_accessed_entries();
	clear_created_entries();

	return res;
}

bool sinsp_plugin::extract_fields(sinsp_evt* evt,
                                  uint32_t num_fields,
                                  ss_plugin_extract_field* fields) {
	return extract_fields_and_offsets(evt, num_fields, fields, nullptr);
}

/** End of Field Extraction CAP **/

/** Event Parsing CAP **/

bool sinsp_plugin::parse_event(sinsp_evt* evt) {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	ss_plugin_event_input ev;
	ev.evt = (const ss_plugin_event*)evt->get_scap_evt();
	ev.evtnum = evt->get_num();
	ev.evtsrc = evt->get_source_name();

	ss_plugin_event_parse_input in;
	ss_plugin_table_reader_vtable_ext table_reader_ext;
	ss_plugin_table_writer_vtable_ext table_writer_ext;
	in.owner = (ss_plugin_owner_t*)this;
	in.get_owner_last_error = sinsp_plugin::get_owner_last_error;
	in.table_reader_ext = &table_reader_ext;
	in.table_writer_ext = &table_writer_ext;
	sinsp_plugin::table_read_api(in.table_reader, table_reader_ext);
	sinsp_plugin::table_write_api(in.table_writer, table_writer_ext);
	auto res = m_handle->api.parse_event(m_state, &ev, &in) == SS_PLUGIN_SUCCESS;

	// do some defensive garbage collection
	clear_ephemeral_tables();
	clear_accessed_entries();
	clear_created_entries();

	return res;
}

/** End of Event Parsing CAP **/

/** Async Events CAP **/

ss_plugin_rc sinsp_plugin::handle_plugin_async_event(ss_plugin_owner_t* o,
                                                     const ss_plugin_event* e,
                                                     char* err) {
	// note: this function can be invoked from different plugin threads,
	// so we need to make sure that every variable we read is either constant
	// during the lifetime of those threads, or that it is atomic.
	auto p = static_cast<sinsp_plugin*>(o);
	auto handler = p->m_async_evt_handler.load();
	if(!(p->caps() & CAP_ASYNC)) {
		if(err) {
			strlcpy(err,
			        "plugin without async events cap used as async handler",
			        PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	}

	if(!handler) {
		if(err) {
			auto e = "async event sent with NULL handler: " + p->name();
			strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	}

	if(e->type != PPME_ASYNCEVENT_E || e->nparams != 3) {
		if(err) {
			auto e = "malformed async event produced by plugin: " + p->name();
			strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	}

	auto name = (const char*)((uint8_t*)e + sizeof(ss_plugin_event) + 4 + 4 + 4 + 4);
	if(p->async_event_names().find(name) == p->async_event_names().end()) {
		if(err) {
			auto e = "incompatible async event '" + std::string(name) +
			         "' produced by plugin: " + p->name();
			strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	}

	try {
		auto evt = std::make_unique<sinsp_evt>();
		ASSERT(evt->get_scap_evt_storage() == nullptr);
		evt->set_scap_evt_storage(new char[e->len]);
		memcpy(evt->get_scap_evt_storage(), e, e->len);
		evt->set_cpuid(0);
		evt->set_num(0);
		evt->set_scap_evt((scap_evt*)evt->get_scap_evt_storage());
		evt->init();
		// note: plugin ID and timestamp will be set by the inspector
		(*handler)(*p, std::move(evt));
	} catch(const std::exception& _e) {
		if(err) {
			strlcpy(err, _e.what(), PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	} catch(...) {
		if(err) {
			strlcpy(err, "unknwon error in pushing async event", PLUGIN_MAX_ERRLEN);
		}
		return SS_PLUGIN_FAILURE;
	}

	return SS_PLUGIN_SUCCESS;
}

bool sinsp_plugin::set_async_event_handler(async_event_handler_t handler) {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	// note: setting the handler before invoking the plugin's function,
	// so that it can be visible to other threads that can potentially
	// be spawned by the plugin for producing async events.
	// In the same way, we need to reset it to NULL after the function
	// returns, to allow any potential extra threads to finish before
	// changing the value. In any case, we make the handler function
	// atomic in case plugin developers fail in stopping async threads.
	//
	// As for the atomic updates to m_async_evt_handler, the possible cases
	// depend on the current handler (CH) and new handler (NH):
	//   - CH null, NH null: the handler value is already null, no updates needed.
	//   - CH null, NH not-null: the handler value must be updated before setting
	//     it to the plugin, so that any newly-spawned thread in the plugin
	//     can see the new value. In case of failure, we should set the handler
	//     to its previous value.
	//   - CH not-null, NH null: the handler value must be updated after setting
	//     it to the plugin, so that any already-running thread in the plugin
	//     can be stopped before setting the handler to null. In case of success,
	//     we can set the handler value to null.
	//   - CH not-null, NH not-null: not supported for now, need to reset
	//     the current handler to null before setting a new one.

	auto cur_handler = m_async_evt_handler.load();
	auto new_handler = (handler != nullptr) ? new async_event_handler_t(handler) : nullptr;

	if(new_handler != nullptr) {
		if(cur_handler != nullptr) {
			delete new_handler;
			throw sinsp_exception("must reset the async event handler before setting a new one");
		}
		m_async_evt_handler.store(new_handler);
	}

	auto callback = (handler != nullptr) ? sinsp_plugin::handle_plugin_async_event : NULL;
	auto rc = m_handle->api.set_async_event_handler(m_state, this, callback);

	if(cur_handler == nullptr && new_handler != nullptr) {
		if(rc != SS_PLUGIN_SUCCESS) {
			// new handler rejected, restore current one and clean up
			m_async_evt_handler.store(cur_handler);
			delete new_handler;
		}
	}

	if(cur_handler != nullptr && new_handler == nullptr) {
		if(rc == SS_PLUGIN_SUCCESS) {
			// new handler accepted, delete current one
			m_async_evt_handler.store(new_handler);
			delete cur_handler;
		}
	}

	return rc == SS_PLUGIN_SUCCESS;
}

bool sinsp_plugin::dump_state(sinsp_dumper& dumper) {
	if(!m_inited) {
		throw sinsp_exception(std::string(s_not_init_err) + ": " + m_name);
	}

	if(!(m_caps & CAP_ASYNC)) {
		throw sinsp_exception("plugin " + m_name + "without async events cap used as dumper");
	}

	if(!m_handle->api.dump_state) {
		return false;
	}

	m_async_dump_handler = [&dumper](auto e) { dumper.dump(e.get()); };

	const auto callback =
	        [](ss_plugin_owner_t* o, const ss_plugin_event* e, char* err) -> ss_plugin_rc {
		auto p = static_cast<sinsp_plugin*>(o);
		// We only support dumping of PPME_ASYNCEVENT_E events
		if(e->type != PPME_ASYNCEVENT_E || e->nparams != 3) {
			if(err) {
				auto e = "malformed async event produced by plugin: " + p->name();
				strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
			}
			return SS_PLUGIN_FAILURE;
		}

		// Event name must be one of the async event names
		auto name = (const char*)((uint8_t*)e + sizeof(ss_plugin_event) + 4 + 4 + 4 + 4);
		if(p->async_event_names().find(name) == p->async_event_names().end()) {
			if(err) {
				auto e = "incompatible async event '" + std::string(name) +
				         "' produced by plugin: " + p->name();
				strlcpy(err, e.c_str(), PLUGIN_MAX_ERRLEN);
			}
			return SS_PLUGIN_FAILURE;
		}

		try {
			auto evt = std::make_unique<sinsp_evt>();
			ASSERT(evt->get_scap_evt_storage() == nullptr);
			evt->set_scap_evt_storage(new char[e->len]);
			memcpy(evt->get_scap_evt_storage(), e, e->len);
			evt->set_cpuid(0);
			evt->set_num(0);
			evt->set_scap_evt((scap_evt*)evt->get_scap_evt_storage());
			evt->init();
			// note: plugin ID and timestamp will be set by the inspector
			p->m_async_dump_handler(std::move(evt));
		} catch(const std::exception& _e) {
			if(err) {
				strlcpy(err, _e.what(), PLUGIN_MAX_ERRLEN);
			}
			return SS_PLUGIN_FAILURE;
		} catch(...) {
			if(err) {
				strlcpy(err, "unknwon error in dumping async event", PLUGIN_MAX_ERRLEN);
			}
			return SS_PLUGIN_FAILURE;
		}
		return SS_PLUGIN_SUCCESS;
	};

	if(m_handle->api.dump_state(m_state, this, callback) != SS_PLUGIN_SUCCESS) {
		throw sinsp_exception("dump error for plugin '" + m_name + "' : " + m_last_owner_err);
	}
	return true;
}
