Add programming interface for event subscription
authorSangwan Kwon <sangwan.kwon@samsung.com>
Wed, 19 Jun 2019 02:54:45 +0000 (11:54 +0900)
committerSangwan Kwon <sangwan.kwon@samsung.com>
Tue, 9 Jul 2019 23:53:47 +0000 (08:53 +0900)
Signed-off-by: Sangwan Kwon <sangwan.kwon@samsung.com>
include/osquery/manager.h
include/osquery/notification.h [new file with mode: 0644]
osquery/CMakeLists.txt
osquery/manager/manager.cpp
osquery/manager/manager_tests.cpp
osquery/notification/CMakeLists.txt [new file with mode: 0644]
osquery/notification/notification.cpp [new file with mode: 0644]
osquery/notification/notification_tests.cpp [new file with mode: 0644]

index b46ae5b..21169c9 100644 (file)
 
 #pragma once
 
-
 #include <string>
 #include <memory>
 #include <map>
 #include <vector>
 
+#include <osquery/notification.h>
+
 namespace osquery {
 
 using Row = std::map<std::string, std::string>;
 using Rows = std::vector<Row>;
 
+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<std::string> tables(void) noexcept;
        static std::vector<std::string> 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<std::string> tablesInternal(void) const noexcept;
        std::vector<std::string> columnsInternal(const std::string& table) const noexcept;
 };
diff --git a/include/osquery/notification.h b/include/osquery/notification.h
new file mode 100644 (file)
index 0000000..3d278a3
--- /dev/null
@@ -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 <map>
+#include <vector>
+#include <functional>
+
+#include <osquery/database.h>
+#include <osquery/status.h>
+#include <osquery/registry.h>
+
+namespace osquery {
+
+using NotifyCallback = std::function<void(const Row&)>;
+
+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<std::string, NotifyCallback> callbacks;
+};
+
+} // namespace osquery
index 1915378..13924a6 100644 (file)
@@ -97,6 +97,7 @@ ADD_SUBDIRECTORY(tables)
 
 # tizen feature
 ADD_SUBDIRECTORY(manager)
+ADD_SUBDIRECTORY(notification)
 
 ## Table generation #############################################################
 FILE(GLOB TABLE_FILES "tables/specs/*.table")
index 7524fad..e239137 100644 (file)
@@ -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<std::string> OsqueryManager::tables(void) noexcept 
 {
        return instance().tablesInternal();
index b52b63b..6efcd70 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include <osquery/notification.h>
 #include <osquery/manager.h>
 #include <osquery/logger.h>
 
@@ -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 (file)
index 0000000..956f83c
--- /dev/null
@@ -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 (file)
index 0000000..77fae79
--- /dev/null
@@ -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 <mutex>
+
+#include <osquery/notification.h>
+#include <osquery/logger.h>
+
+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<std::mutex> 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<std::mutex> 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 (file)
index 0000000..0c21df2
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+#include <osquery/notification.h>
+#include <osquery/logger.h>
+
+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();
+}