From 763f1ae027b5a22469837dc3526926ba007cb8b9 Mon Sep 17 00:00:00 2001 From: Sangwan Kwon Date: Wed, 19 Jun 2019 11:54:45 +0900 Subject: [PATCH] Add programming interface for event subscription Signed-off-by: Sangwan Kwon --- include/osquery/manager.h | 22 +++++++-- include/osquery/notification.h | 56 +++++++++++++++++++++ osquery/CMakeLists.txt | 1 + osquery/manager/manager.cpp | 14 ++++++ osquery/manager/manager_tests.cpp | 24 +++++++++ osquery/notification/CMakeLists.txt | 17 +++++++ osquery/notification/notification.cpp | 64 ++++++++++++++++++++++++ osquery/notification/notification_tests.cpp | 77 +++++++++++++++++++++++++++++ 8 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 include/osquery/notification.h create mode 100644 osquery/notification/CMakeLists.txt create mode 100644 osquery/notification/notification.cpp create mode 100644 osquery/notification/notification_tests.cpp diff --git a/include/osquery/manager.h b/include/osquery/manager.h index b46ae5b..21169c9 100644 --- a/include/osquery/manager.h +++ b/include/osquery/manager.h @@ -23,27 +23,37 @@ #pragma once - #include #include #include #include +#include + namespace osquery { using Row = std::map; using Rows = std::vector; +using Callback = NotifyCallback; + +/// TBD: Consider error handling. class OsqueryManager final { public: - OsqueryManager(const OsqueryManager&) = delete; - OsqueryManager& operator=(const OsqueryManager&) = delete; - - /// TBD: Consider error handling. + /// Query Execution method static Rows execute(const std::string& query); + + /// Event Subscription method + static void subscribe(const std::string& table, const Callback& callback); + + /// Table information static std::vector tables(void) noexcept; static std::vector columns(const std::string& table) noexcept; +public: + OsqueryManager(const OsqueryManager&) = delete; + OsqueryManager& operator=(const OsqueryManager&) = delete; + private: OsqueryManager(); ~OsqueryManager() noexcept; @@ -52,6 +62,8 @@ private: /// TODO(Sangwan): Apply pimpl idiom. Rows executeInternal(const std::string& query) const; + void subscribeInternal(const std::string& table, const Callback& callback); + std::vector tablesInternal(void) const noexcept; std::vector columnsInternal(const std::string& table) const noexcept; }; diff --git a/include/osquery/notification.h b/include/osquery/notification.h new file mode 100644 index 0000000..3d278a3 --- /dev/null +++ b/include/osquery/notification.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., 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 + */ + + +/** + * @file notification.h + * @brief Notify to registered stuffs when event-callback called + */ + + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace osquery { + +using NotifyCallback = std::function; + +class Notification final { +public: + static Notification& instance(); + + Status add(const std::string& table, const NotifyCallback& callback); + Status emit(const std::string& table, const Row& result) const; + +public: + Notification(const Notification&) = delete; + Notification& operator=(const Notification&) = delete; + +private: + Notification() = default; + ~Notification() = default; + + std::multimap callbacks; +}; + +} // namespace osquery diff --git a/osquery/CMakeLists.txt b/osquery/CMakeLists.txt index 1915378..13924a6 100644 --- a/osquery/CMakeLists.txt +++ b/osquery/CMakeLists.txt @@ -97,6 +97,7 @@ ADD_SUBDIRECTORY(tables) # tizen feature ADD_SUBDIRECTORY(manager) +ADD_SUBDIRECTORY(notification) ## Table generation ############################################################# FILE(GLOB TABLE_FILES "tables/specs/*.table") diff --git a/osquery/manager/manager.cpp b/osquery/manager/manager.cpp index 7524fad..e239137 100644 --- a/osquery/manager/manager.cpp +++ b/osquery/manager/manager.cpp @@ -81,6 +81,20 @@ Rows OsqueryManager::executeInternal(const std::string& query) const return results; } +void OsqueryManager::subscribe(const std::string& table, const Callback& callback) +{ + LOG(INFO) << "Subscribe event: " << table; + + return instance().subscribeInternal(table, callback); +} + +void OsqueryManager::subscribeInternal(const std::string& table, const Callback& callback) +{ + auto status = Notification::instance().add(table, callback); + if (!status.ok()) + LOG(ERROR) << "Subscribing event failed: " << status.getCode(); +} + std::vector OsqueryManager::tables(void) noexcept { return instance().tablesInternal(); diff --git a/osquery/manager/manager_tests.cpp b/osquery/manager/manager_tests.cpp index b52b63b..6efcd70 100644 --- a/osquery/manager/manager_tests.cpp +++ b/osquery/manager/manager_tests.cpp @@ -16,6 +16,7 @@ #include +#include #include #include @@ -34,6 +35,29 @@ TEST_F(ManagerTests, test_manager_execute) { VLOG(1) << "\t seconds: " << rows[0]["seconds"]; } +TEST_F(ManagerTests, test_manager_subscribe) { + int called = 0; + auto callback = [&](const Row& row) { + VLOG(1) << "NotifyCallback called:"; + for (const auto& r : row) + VLOG(1) << "\t" << r.first << " : " << r.second; + called++; + }; + + OsqueryManager::subscribe("manager_test", callback); + + Row row; + row["foo"] = "bar"; + row["foo2"] = "bar2"; + row["foo3"] = "bar3"; + + /// Notification trigger + auto s = Notification::instance().emit("manager_test", row); + + EXPECT_TRUE(s.ok()); + EXPECT_EQ(called, 1); +} + TEST_F(ManagerTests, test_manager_tables) { auto tables = OsqueryManager::tables(); EXPECT_TRUE(tables.size() > 0); diff --git a/osquery/notification/CMakeLists.txt b/osquery/notification/CMakeLists.txt new file mode 100644 index 0000000..956f83c --- /dev/null +++ b/osquery/notification/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2019 Samsung Electronics Co., 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 + +ADD_OSQUERY_LIBRARY(TRUE osquery_notification notification.cpp) + +ADD_OSQUERY_TEST(TRUE osquery_notification_tests notification_tests.cpp) diff --git a/osquery/notification/notification.cpp b/osquery/notification/notification.cpp new file mode 100644 index 0000000..77fae79 --- /dev/null +++ b/osquery/notification/notification.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include + +#include +#include + +namespace { + std::mutex mutex; +} // anonymous namespace + +namespace osquery { + +Notification& Notification::instance() +{ + static Notification notifier; + return notifier; +} + +Status Notification::add(const std::string& table, const NotifyCallback& callback) +{ + if (table.empty()) + return Status(1, "Wrong table name"); + + LOG(INFO) << "Add NotifyCallback to:" << table; + { + std::lock_guard lock(mutex); + this->callbacks.insert(std::make_pair(table, callback)); + } + + return Status(0, "OK"); +} + +Status Notification::emit(const std::string& table, const Row& result) const +{ + if (table.empty()) + return Status(1, "Wrong table name"); + + auto iter = this->callbacks.find(table); + if (iter == this->callbacks.end()) + return Status(1, "Registered callback not found"); + + LOG(INFO) << "Emit notification about:" << table; + { + std::lock_guard lock(mutex); + while (iter != this->callbacks.end()) { + const auto& callback = iter->second; + callback(result); + iter++; + } + } + + return Status(0, "OK"); +} + +} // namespace osquery diff --git a/osquery/notification/notification_tests.cpp b/osquery/notification/notification_tests.cpp new file mode 100644 index 0000000..0c21df2 --- /dev/null +++ b/osquery/notification/notification_tests.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., 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 + +#include +#include + +using namespace osquery; + +class NotificationTests : public testing::Test {}; + +TEST_F(NotificationTests, test_add_positive) { + auto& notifier = Notification::instance(); + + auto callback = [](const Row& row) { + VLOG(1) << "NotifyCallback called:"; + for (const auto& r : row) + VLOG(1) << "\t" << r.first << " : " << r.second; + }; + + auto s = notifier.add("test", std::move(callback)); + EXPECT_TRUE(s.ok()); +} + +TEST_F(NotificationTests, test_add_negative) { + auto& notifier = Notification::instance(); + + auto callback = [](const Row& row) { + VLOG(1) << "NotifyCallback called:"; + for (const auto& r : row) + VLOG(1) << "\t" << r.first << " : " << r.second; + }; + + auto s = notifier.add("", std::move(callback)); + EXPECT_FALSE(s.ok()); +} + +TEST_F(NotificationTests, test_emit_positive) { + auto& notifier = Notification::instance(); + + int called = 0; + auto callback = [&](const Row& row) { + VLOG(1) << "NotifyCallback called:"; + for (const auto& r : row) + VLOG(1) << "\t" << r.first << " : " << r.second; + called++; + }; + + auto s = notifier.add("test2", std::move(callback)); + EXPECT_TRUE(s.ok()); + + Row row; + row["foo"] = "bar"; + s = notifier.emit("test2", row); + + EXPECT_TRUE(s.ok()); + EXPECT_EQ(called, 1); +} + +int main(int argc, char* argv[]) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- 2.7.4