From 5fa6a48ce9745ffb8197fb3204c5fbb687fb2797 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Tue, 3 Jun 2014 09:49:07 +0200 Subject: [PATCH] Various changes to InputMonitor [Bug/Feature] InputMonitor had to be corrected [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: Ibb7f71da0cfc19dad943a9e69badda3b6d866d70 Signed-off-by: Jan Olszak --- common/dbus/connection.cpp | 46 +--- common/utils/fs.cpp | 17 -- common/utils/scoped-gerror.cpp | 72 ++++++ common/utils/scoped-gerror.hpp | 73 ++++++ server/CMakeLists.txt | 3 +- server/configs/daemon.conf | 2 +- server/input-monitor-config.hpp | 19 +- server/input-monitor.cpp | 362 +++++++++++++++------------ server/input-monitor.hpp | 47 ++-- tests/unit_tests/CMakeLists.txt | 2 +- tests/unit_tests/server/ut-input-monitor.cpp | 267 ++++++-------------- 11 files changed, 466 insertions(+), 444 deletions(-) create mode 100644 common/utils/scoped-gerror.cpp create mode 100644 common/utils/scoped-gerror.hpp diff --git a/common/dbus/connection.cpp b/common/dbus/connection.cpp index 03513cc..479c253 100644 --- a/common/dbus/connection.cpp +++ b/common/dbus/connection.cpp @@ -26,8 +26,10 @@ #include "dbus/connection.hpp" #include "dbus/exception.hpp" #include "utils/callback-wrapper.hpp" +#include "utils/scoped-gerror.hpp" #include "log/logger.hpp" +using namespace security_containers::utils; namespace security_containers { namespace dbus { @@ -41,40 +43,6 @@ const std::string INTROSPECT_METHOD = "Introspect"; const int CALL_METHOD_TIMEOUT_MS = 1000; -class ScopedError { -public: - ScopedError() : mError(NULL) {} - ~ScopedError() - { - if (mError) { - g_error_free(mError); - } - } - void strip() - { - g_dbus_error_strip_remote_error(mError); - } - operator bool () const - { - return mError; - } - GError** operator& () - { - return &mError; - } - const GError* operator->() const - { - return mError; - } - friend std::ostream& operator<<(std::ostream& os, const ScopedError& e) - { - os << e->message; - return os; - } -private: - GError* mError; -}; - class MethodResultBuilderImpl : public MethodResultBuilder { public: MethodResultBuilderImpl(GDBusMethodInvocation* invocation) @@ -102,7 +70,7 @@ private: bool mResultSet; }; -void throwDbusException(const ScopedError& e) +void throwDbusException(const ScopedGError& e) { if (e->domain == g_io_error_quark()) { if (e->code == G_IO_ERROR_DBUS_ERROR) { @@ -135,7 +103,7 @@ DbusConnection::DbusConnection(const std::string& address) : mConnection(NULL) , mNameId(0) { - ScopedError error; + ScopedGError error; const GDBusConnectionFlags flags = static_cast(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION); @@ -200,7 +168,7 @@ void DbusConnection::emitSignal(const std::string& objectPath, const std::string& name, GVariant* parameters) { - ScopedError error; + ScopedGError error; g_dbus_connection_emit_signal(mConnection, NULL, objectPath.c_str(), @@ -264,7 +232,7 @@ void DbusConnection::registerObject(const std::string& objectPath, const std::string& objectDefinitionXml, const MethodCallCallback& callback) { - ScopedError error; + ScopedGError error; GDBusNodeInfo* nodeInfo = g_dbus_node_info_new_for_xml(objectDefinitionXml.c_str(), &error); if (nodeInfo != NULL && (nodeInfo->interfaces == NULL || nodeInfo->interfaces[0] == NULL || @@ -333,7 +301,7 @@ GVariantPtr DbusConnection::callMethod(const std::string& busName, GVariant* parameters, const std::string& replyType) { - ScopedError error; + ScopedGError error; GVariant* result = g_dbus_connection_call_sync(mConnection, busName.c_str(), objectPath.c_str(), diff --git a/common/utils/fs.cpp b/common/utils/fs.cpp index 4fe8363..57c8e6d 100644 --- a/common/utils/fs.cpp +++ b/common/utils/fs.cpp @@ -101,23 +101,6 @@ bool isCharDevice(const std::string& path) return ::stat(path.c_str(), &s) == 0 && S_IFCHR == (s.st_mode & S_IFMT); } -bool listDir(const std::string& path, std::vector& files) -{ - DIR* dirp = ::opendir(path.c_str()); - if (dirp == NULL) { - LOGE("Could not open directory" << path << "': " << strerror(errno)); - return false; - } - - struct dirent* entry; - while ((entry = ::readdir(dirp)) != NULL) { - files.push_back(entry->d_name); - } - - ::closedir(dirp); - return true; -} - namespace { // NOTE: Should be the same as in systemd/src/core/mount-setup.c const std::string RUN_MOUNT_POINT_OPTIONS = "mode=755,smackfstransmute=System::Run"; diff --git a/common/utils/scoped-gerror.cpp b/common/utils/scoped-gerror.cpp new file mode 100644 index 0000000..303dd4e --- /dev/null +++ b/common/utils/scoped-gerror.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * 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 + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Implementation of the wrapper for GError + */ + +#include "config.hpp" + +#include "scoped-gerror.hpp" + +namespace security_containers { +namespace utils { + +ScopedGError::ScopedGError() + : mError(NULL) +{ + +} + +ScopedGError::~ScopedGError() +{ + if (mError) { + g_error_free(mError); + } +} + +bool ScopedGError::strip() +{ + return g_dbus_error_strip_remote_error(mError); +} + +ScopedGError::operator bool () const +{ + return mError != nullptr; +} + +GError** ScopedGError::operator& () +{ + return &mError; +} + +const GError* ScopedGError::operator->() const +{ + return mError; +} + +std::ostream& operator<<(std::ostream& os, const ScopedGError& e) +{ + os << e->message; + return os; +} + +} // namespace utils +} // namespace security_containers diff --git a/common/utils/scoped-gerror.hpp b/common/utils/scoped-gerror.hpp new file mode 100644 index 0000000..06e17aa --- /dev/null +++ b/common/utils/scoped-gerror.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Jan Olszak + * + * 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 + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Declaration of the wrapper for GError + */ + +#ifndef COMMON_SCOPED_GERROR_HPP +#define COMMON_SCOPED_GERROR_HPP + +#include +#include + +namespace security_containers{ +namespace utils { + +class ScopedGError { +public: + ScopedGError(); + ~ScopedGError(); + + /** + * Strip the error + */ + bool strip(); + + /** + * Is error pointer NULL? + */ + operator bool () const; + + /** + * @return pointer to the GError + */ + GError** operator& (); + + /** + * @return the GError + */ + const GError* operator->() const; + + /** + * Writes out the error message + * @param os the output stream + * @param e error to write out + */ + friend std::ostream& operator<<(std::ostream& os, const ScopedGError& e); + +private: + GError* mError; +}; + +} // namespace utils +} // namespace security_containers + +#endif // COMMON_SCOPED_GERROR_HPP diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 42d9503..b93b010 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -28,8 +28,7 @@ ADD_EXECUTABLE(${SERVER_CODENAME} ${project_SRCS} ${common_SRCS}) ## Link libraries ############################################################## -FIND_PACKAGE (Boost COMPONENTS program_options system filesystem) - +FIND_PACKAGE (Boost COMPONENTS program_options system filesystem regex) PKG_CHECK_MODULES(SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 libsystemd-journal) INCLUDE_DIRECTORIES(${COMMON_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${SERVER_DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) diff --git a/server/configs/daemon.conf b/server/configs/daemon.conf index c68a9f3..2610f5e 100644 --- a/server/configs/daemon.conf +++ b/server/configs/daemon.conf @@ -2,7 +2,7 @@ "containerConfigs" : ["containers/private.conf", "containers/business.conf" ], "foregroundId" : "private", "inputConfig" : {"enabled" : true, - "device" : "gpio-keys.4", + "device" : "gpio-keys", "code" : 139, "numberOfEvents" : 2, "timeWindowMs" : 500} diff --git a/server/input-monitor-config.hpp b/server/input-monitor-config.hpp index e210127..33e716f 100644 --- a/server/input-monitor-config.hpp +++ b/server/input-monitor-config.hpp @@ -36,15 +36,30 @@ namespace security_containers { struct InputConfig { + /** + * Is monitoring input enabled? + */ bool enabled; /** - * represents either a device name or an absolute device file path (must be a string starting - * with '/' character) + * Device name or an absolute device file path + * (must be a string starting with '/' character) */ std::string device; + + /** + * Event code + */ int code; + + /** + * Number of events that will trigger an action + */ int numberOfEvents; + + /** + * Time window in which subsequent events will trigger an action + */ int timeWindowMs; CONFIG_REGISTER diff --git a/server/input-monitor.cpp b/server/input-monitor.cpp index ec8c300..13025a5 100644 --- a/server/input-monitor.cpp +++ b/server/input-monitor.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved * - * Contact: Pawel Broda + * Contact: Jan Olszak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,21 @@ /** * @file - * @author Pawel Broda (p.broda@partner.samsung.com) + * @author Jan Olszak (j.olszak@samsung.com) * @brief C++ wrapper for glib input monitor */ -#include "exception.hpp" -#include "input-monitor.hpp" +#include "config.hpp" + #include "input-monitor-config.hpp" +#include "input-monitor.hpp" +#include "exception.hpp" #include "log/logger.hpp" #include "utils/exception.hpp" #include "utils/fs.hpp" +#include "utils/callback-wrapper.hpp" +#include "utils/scoped-gerror.hpp" #include #include @@ -39,225 +43,253 @@ #include #include #include + +#include +#include #include +#include +using namespace security_containers::utils; +namespace fs = boost::filesystem; namespace security_containers { -// TODO: extract device helper and monitoring utilities from InputMonitor to separate files - +namespace { +const int MAX_TIME_WINDOW_SEC = 10; +const int KEY_PRESSED = 1; +const int DEVICE_NAME_LENGTH = 256; +const int MAX_NUMBER_OF_EVENTS = 10; +const std::string DEVICE_DIR = "/dev/input/"; -const std::string InputMonitor::DEVICE_DIR = "/dev/input/"; +} // namespace -std::string InputMonitor::findDeviceNode(const std::string& deviceName) +InputMonitor::InputMonitor(const InputConfig& inputConfig, + const NotifyCallback& notifyCallback) + : mConfig(inputConfig), + mNotifyCallback(notifyCallback) { - std::vector files; - - try { - utils::listDir(DEVICE_DIR, files); - } catch (UtilsException&) { + if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) { + LOGE("Time window exceeds maximum: " << MAX_TIME_WINDOW_SEC); throw InputMonitorException(); } - for (auto fileName: files) { - std::string fullDevicePath = DEVICE_DIR + fileName; - if (utils::isCharDevice(fullDevicePath)) { - char name[DEVICE_NAME_LENGTH]; - memset(name, 0, sizeof(name)); - int fd = open(fullDevicePath.c_str(), O_RDONLY); - if (fd < 0) { - LOGD("Failed to open'" << fullDevicePath << "'"); - continue; - } - - if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { - LOGD("Failed to send ioctl with request about device name to '" - << fullDevicePath << "'"); - close(fd); - continue; - } - - close(fd); - - if (deviceName == name) { - LOGI("Device file for '" << deviceName << "' found under '" - << fullDevicePath << "'"); - return fullDevicePath; - } - } + if (mConfig.numberOfEvents > MAX_NUMBER_OF_EVENTS) { + LOGE("Number of events exceeds maximum: " << MAX_NUMBER_OF_EVENTS); + throw InputMonitorException(); } - return std::string(); -} + std::string devicePath = getDevicePath(); -bool InputMonitor::detectExpectedEvent(const struct input_event& ie) -{ - // log events. Example log: - // Event detected [/dev/input/event4]: - // time: 946705528.918966 sec - // type, code, value: 1, 139, 1 - LOGT("Event detected [" << mConfig.device.c_str() << "]:\n" - << "\ttime: " << ie.time.tv_sec << "." << ie.time.tv_usec << " sec\n" - << "\ttype, code, value: " << ie.type << ", " << ie.code << ", " << ie.value); - - if (ie.type == EV_KEY && - ie.code == mConfig.code && - ie.value == KEY_PRESSED) { - for (int i = mConfig.numberOfEvents - 1; i > 0; --i) { - mEventTime[i].tv_sec = mEventTime[i - 1].tv_sec; - mEventTime[i].tv_usec = mEventTime[i - 1].tv_usec; - } - - mEventTime[0].tv_sec = ie.time.tv_sec; - mEventTime[0].tv_usec = ie.time.tv_usec; - - // a) check, if given event sequence happened within specified time window - // (this is necessary, because of multiplying in the next step) - // b) if yes, then compute the difference between the first and the last one - if (((mEventTime[0].tv_sec - mEventTime[mConfig.numberOfEvents - 1].tv_sec) < MAX_TIME_WINDOW_SEC) && - (mEventTime[0].tv_sec - mEventTime[mConfig.numberOfEvents - 1].tv_sec) * 1000000L + - mEventTime[0].tv_usec - mEventTime[mConfig.numberOfEvents - 1].tv_usec < mConfig.timeWindowMs * 1000L) { + LOGT("Input monitor configuration: \n" + << "\tenabled: " << mConfig.enabled << "\n" + << "\tdevice: " << mConfig.device << "\n" + << "\tpath: " << devicePath << "\n" + << "\ttype: " << EV_KEY << "\n" + << "\tcode: " << mConfig.code << "\n" + << "\tvalue: " << KEY_PRESSED << "\n" + << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n" + << "\ttimeWindowMs: " << mConfig.timeWindowMs); - resetEventsTime(); + createGIOChannel(devicePath); +} - return true; - } +InputMonitor::~InputMonitor() +{ + LOGD("Destroying InputMonitor"); + ScopedGError error; + g_io_channel_unref(mChannelPtr); + if (g_io_channel_shutdown(mChannelPtr, FALSE, &error) + != G_IO_STATUS_NORMAL) { + LOGE("Error during shutting down GIOChannel: " << error->message); } - return false; + if (!g_source_remove(mSourceId)) { + LOGE("Error during removing the source"); + } } -void InputMonitor::readDevice(GIOChannel* gio) +namespace { +bool isDeviceWithName(const boost::regex& deviceNameRegex, + const fs::directory_entry& directoryEntry) { - struct input_event ie; - gchar buff[sizeof(struct input_event)]; - gsize nBytesRequested = sizeof(struct input_event); - gsize nBytesRead = 0; - gsize nBytesReadTotal = 0; - GError *err = NULL; - - do { - GIOStatus readStatus = g_io_channel_read_chars(gio, - &buff[nBytesReadTotal], - nBytesRequested, - &nBytesRead, - &err); + std::string path = directoryEntry.path().string(); - if (readStatus == G_IO_STATUS_ERROR || err != NULL) { - LOGE("Read from input monitor channel failed"); - return; - } + if (!utils::isCharDevice(path)) { + return false; + } - nBytesRequested -= nBytesRead; - nBytesReadTotal += nBytesRead; - } while (nBytesRequested > 0); + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) { + LOGD("Failed to open " << path); + return false; + } - memcpy(&ie, buff, sizeof(struct input_event)); + char name[DEVICE_NAME_LENGTH]; + if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { + LOGD("Failed to get the device name of: " << path); + if (close(fd) < 0) { + LOGE("Error during closing file " << path); + } + return false; + } - if (detectExpectedEvent(ie)) { - LOGI("Input monitor detected pattern."); - mNotifyCallback(); + if (close(fd) < 0) { + LOGE("Error during closing file " << path); } -} -gboolean InputMonitor::readDeviceCallback(GIOChannel* gio, - GIOCondition /*condition*/, - gpointer data) -{ - InputMonitor& inputMonitor = *reinterpret_cast(data); - inputMonitor.readDevice(gio); + LOGD("Checking device: " << name); + if (boost::regex_match(name, deviceNameRegex)) { + LOGI("Device file found under: " << path); + return true; + } - return TRUE; + return false; } +} // namespace -InputMonitor::InputMonitor(const InputConfig& inputConfig, - const InputNotifyCallback& inputNotifyCallback) +std::string InputMonitor::getDevicePath() { - mConfig = inputConfig; - mNotifyCallback = inputNotifyCallback; - - resetEventsTime(); - - if (mConfig.numberOfEvents > MAX_NUMBER_OF_EVENTS) { - LOGE("Input monitor numberOfEvents > MAX_NUMBER_OF_EVENTS\n" - << "\twhere MAX_NUMBER_OF_EVENTS = " << MAX_NUMBER_OF_EVENTS); - throw InputMonitorException(); + std::string device = mConfig.device; + if (fs::path(device).is_absolute() + && fs::exists(device)) { + LOGD("Device file path is given"); + return device; } - if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) { - LOGE("Input monitor timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L\n" - << "\twhere MAX_TIME_WINDOW_SEC = " << MAX_TIME_WINDOW_SEC); + // device name is given - device file path is to be determined + LOGT("Determining, which device node is assigned to '" << device << "'"); + using namespace std::placeholders; + fs::directory_iterator end; + boost::regex deviceNameRegex(".*" + device + ".*"); + const auto it = std::find_if(fs::directory_iterator(DEVICE_DIR), + end, + std::bind(isDeviceWithName, deviceNameRegex, _1)); + if (it == end) { + LOGE("None of the files under '" << DEVICE_DIR << + "' represents device named: " << device); throw InputMonitorException(); } - std::string devicePath; - if (mConfig.device[0] == '/') { // device file path is given - devicePath = mConfig.device; - } else { // device name is given - device file path is to be determined - LOGT("Determining, which device node is assigned to '" << mConfig.device << "'"); - devicePath = findDeviceNode(mConfig.device); - if (devicePath.empty()) { - LOGE("None of the files under '" << DEVICE_DIR << "' represents device named: " - << mConfig.device.c_str()); - throw InputMonitorException(); - } - } + return it->path().string(); +} - LOGT("Input monitor configuration: \n" - << "\tenabled: " << mConfig.enabled << "\n" - << "\tdevice: " << mConfig.device.c_str() << "\n" - << "\tpath: " << devicePath.c_str() << "\n" - << "\ttype: " << EV_KEY << "\n" - << "\tcode: " << mConfig.code << "\n" - << "\tvalue: " << KEY_PRESSED << "\n" - << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n" - << "\ttimeWindowMs: " << mConfig.timeWindowMs); - GError *err = NULL; - // creating channel using steps below, i.e.: - // 1) open() - // 2) g_io_channel_unix_new() - // was chosen, because glib API does not allow to open a file - // in a non-blocking mode. There is no argument to pass additional - // flags and we need such a mode for FIFOs in the tests. +void InputMonitor::createGIOChannel(const std::string& devicePath) +{ + // We need NONBLOCK for FIFOs in the tests int fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK); if (fd < 0) { - LOGE("Cannot create input monitor channel. Device file: " << devicePath.c_str() - << " doesn't exist" ); + LOGE("Cannot create input monitor channel. Device file: " << + devicePath << " doesn't exist"); throw InputMonitorException(); } - GIOChannel *mChannel = g_io_channel_unix_new(fd); + mChannelPtr = g_io_channel_unix_new(fd); - // close the channel on final unref - g_io_channel_set_close_on_unref(mChannel, - TRUE); - - // read binary - g_io_channel_set_encoding(mChannel, - NULL, - &err); - - if (err != NULL) { - LOGE("Cannot set encoding for input monitor channel"); + // Read binary data + if (g_io_channel_set_encoding(mChannelPtr, + NULL, + NULL) != G_IO_STATUS_NORMAL) { + LOGE("Cannot set encoding for input monitor channel "); throw InputMonitorException(); } - if (!g_io_add_watch(mChannel, G_IO_IN, readDeviceCallback, this)) { + using namespace std::placeholders; + ReadDeviceCallback callback = std::bind(&InputMonitor::readDevice, this, _1); + // Add the callback + mSourceId = g_io_add_watch_full(mChannelPtr, + G_PRIORITY_DEFAULT, + G_IO_IN, + readDeviceCallback, + utils::createCallbackWrapper(callback, mGuard.spawn()), + &utils::deleteCallbackWrapper); + if (!mSourceId) { LOGE("Cannot add watch on device input file"); throw InputMonitorException(); } } -void InputMonitor::resetEventsTime() +gboolean InputMonitor::readDeviceCallback(GIOChannel* gio, + GIOCondition /*condition*/, + gpointer data) { - memset(mEventTime, 0, sizeof(struct timeval) * MAX_NUMBER_OF_EVENTS); + const ReadDeviceCallback& callback = utils::getCallbackFromPointer(data); + callback(gio); + return TRUE; } -InputMonitor::~InputMonitor() +void InputMonitor::readDevice(GIOChannel* gio) { + struct input_event ie; + gsize nBytesReadTotal = 0; + gsize nBytesRequested = sizeof(struct input_event); + ScopedGError error; + + do { + gsize nBytesRead = 0; + GIOStatus readStatus = g_io_channel_read_chars(gio, + &reinterpret_cast(&ie)[nBytesReadTotal], + nBytesRequested, + &nBytesRead, + &error); + + if (readStatus == G_IO_STATUS_ERROR) { + LOGE("Read from input monitor channel failed: " << error->message); + return; + } + + nBytesRequested -= nBytesRead; + nBytesReadTotal += nBytesRead; + } while (nBytesRequested > 0); + + + if (isExpectedEventSequence(ie)) { + LOGI("Input monitor detected pattern."); + mNotifyCallback(); + } } +bool InputMonitor::isExpectedEventSequence(const struct input_event& ie) +{ + LOGT("Event detected [" << mConfig.device.c_str() << "]:\n" + << "\ttime: " << ie.time.tv_sec << "." << ie.time.tv_usec << " sec\n" + << "\ttype, code, value: " << ie.type << ", " << ie.code << ", " << ie.value); + + if (ie.type != EV_KEY + || ie.code != mConfig.code + || ie.value != KEY_PRESSED) { + LOGT("Wrong kind of event"); + return false; + } + + mEventTimes.push_back(ie.time); + + if (mEventTimes.size() < static_cast(mConfig.numberOfEvents)) { + LOGT("Event sequence too short"); + return false; + } + + struct timeval oldest = mEventTimes.front(); + mEventTimes.pop_front(); + struct timeval latest = mEventTimes.back(); + + long int secDiff = latest.tv_sec - oldest.tv_sec; + if (secDiff >= MAX_TIME_WINDOW_SEC) { + LOGT("Time window exceeded"); + return false; + } + + long int timeDiff = secDiff * 1000L; + timeDiff += static_cast((latest.tv_usec - oldest.tv_usec) / 1000L); + if (timeDiff < mConfig.timeWindowMs) { + LOGD("Event sequence detected"); + mEventTimes.clear(); + return true; + } + + LOGT("Event sequence not detected"); + return false; +} } // namespace security_containers diff --git a/server/input-monitor.hpp b/server/input-monitor.hpp index 692a554..15262bc 100644 --- a/server/input-monitor.hpp +++ b/server/input-monitor.hpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved * - * Contact: Pawel Broda + * Contact: Jan Olszak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,50 +18,53 @@ /** * @file - * @author Pawel Broda (p.broda@partner.samsung.com) + * @author Jan Olszak (j.olszak@samsung.com) * @brief C++ wrapper for glib input monitor */ #ifndef SERVER_INPUT_MONITOR_HPP #define SERVER_INPUT_MONITOR_HPP - #include "input-monitor-config.hpp" +#include "utils/callback-guard.hpp" -#include -#include #include #include +#include + +#include +#include +#include namespace security_containers { - class InputMonitor { public: - typedef std::function InputNotifyCallback; - InputMonitor(const InputConfig&, const InputNotifyCallback&); + typedef std::function NotifyCallback; + + InputMonitor(const InputConfig& inputConfig, + const NotifyCallback& notifyCallback); ~InputMonitor(); private: - static const int MAX_NUMBER_OF_EVENTS = 5; - static const int MAX_TIME_WINDOW_SEC = 10; - static const int KEY_PRESSED = 1; - static const int DEVICE_NAME_LENGTH = 256; - static const std::string DEVICE_DIR; - struct timeval mEventTime[MAX_NUMBER_OF_EVENTS]; - GIOChannel *mChannel; + typedef std::function ReadDeviceCallback; + InputConfig mConfig; + NotifyCallback mNotifyCallback; + + std::list mEventTimes; + GIOChannel* mChannelPtr; - // External callback to be registered at InputMonitor instance - InputNotifyCallback mNotifyCallback; + std::string getDevicePath(); + void createGIOChannel(const std::string& devicePath); // Internal callback to be registered at glib g_io_add_watch() - static gboolean readDeviceCallback(GIOChannel *, GIOCondition, gpointer); - std::string findDeviceNode(const std::string&); - bool detectExpectedEvent(const struct input_event&); - void readDevice(GIOChannel *); - void resetEventsTime(); + static gboolean readDeviceCallback(GIOChannel*, GIOCondition, gpointer); + bool isExpectedEventSequence(const struct input_event&); + void readDevice(GIOChannel*); + utils::CallbackGuard mGuard; + guint mSourceId; }; } // namespace security_containers diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 816f058..3be44b7 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -32,7 +32,7 @@ ADD_EXECUTABLE(${UT_SERVER_CODENAME} ${project_SRCS} ${common_SRCS} ${server_SRC ## Link libraries ############################################################## -FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem) +FIND_PACKAGE (Boost COMPONENTS unit_test_framework system filesystem regex) PKG_CHECK_MODULES(UT_SERVER_DEPS REQUIRED libvirt libvirt-glib-1.0 json gio-2.0 libsystemd-journal) INCLUDE_DIRECTORIES(${COMMON_FOLDER} ${SERVER_FOLDER} ${UNIT_TESTS_FOLDER}) diff --git a/tests/unit_tests/server/ut-input-monitor.cpp b/tests/unit_tests/server/ut-input-monitor.cpp index 04191a7..a0bd9dd 100644 --- a/tests/unit_tests/server/ut-input-monitor.cpp +++ b/tests/unit_tests/server/ut-input-monitor.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved * - * Contact: Pawel Broda + * Contact: Jan Olszak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ /** * @file - * @author Pawel Broda (p.broda@partner.samsung.com) + * @author Jan Olszak (j.olszak@samsung.com) * @brief Unit tests of the InputMonitor class */ @@ -33,96 +33,41 @@ #include "utils/glib-loop.hpp" #include "utils/latch.hpp" -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include #include +#include +#include +#include +#include +#include -BOOST_AUTO_TEST_SUITE(InputMonitorSuite) using namespace security_containers; using namespace security_containers::utils; namespace { -const char* TEST_INPUT_DEVICE = "/tmp/testInputDevice"; +std::string TEST_INPUT_DEVICE = + boost::filesystem::unique_path("/tmp/testInputDevice-%%%%").string(); const int EVENT_TYPE = 1; const int EVENT_CODE = 139; const int EVENT_BUTTON_RELEASED = 0; const int EVENT_BUTTON_PRESSED = 1; -const unsigned int ONE_EVENT = 1; -const unsigned int TEN_EVENTS = 10; - -const int SINGLE_EVENT_TIMEOUT = 10; - -std::atomic counter(0); - - -void callbackEmpty() -{ -} - -void callbackCounter() -{ - counter++; -} - -void resetCounter() -{ - counter = 0; -} - -unsigned int getCounter() -{ - return counter; -} - -/* - * A wrapper for write() which restarts after EINTR. - */ -ssize_t safeWrite(int fd, const void *buff, size_t len) -{ - size_t totalBytesWritten = 0; - - while (len > 0) { - ssize_t bytesWritten = write(fd, buff, len); - - if (bytesWritten < 0 && errno == EINTR) { - continue; - } - - if (bytesWritten < 0) { - return bytesWritten; - } - - if (bytesWritten == 0) { - return totalBytesWritten; - } - - buff = (const char *)buff + bytesWritten; - len -= bytesWritten; - totalBytesWritten += bytesWritten; - } +const int SINGLE_EVENT_TIMEOUT = 1000; - return totalBytesWritten; -} - -class Fixture { -public: +struct Fixture { InputConfig inputConfig; utils::ScopedGlibLoop mLoop; - struct input_event ie[2]; + struct input_event ie; - Fixture() { + Fixture() + { inputConfig.numberOfEvents = 2; inputConfig.device = TEST_INPUT_DEVICE; inputConfig.code = EVENT_CODE; @@ -130,204 +75,136 @@ public: inputConfig.timeWindowMs = 500; // fill simulated events with init values - ie[0].time.tv_sec = 946707544; - ie[0].time.tv_usec = 0; - ie[0].type = EVENT_TYPE; - ie[0].code = EVENT_CODE; - ie[0].value = EVENT_BUTTON_RELEASED; - - ie[1].time.tv_sec = 946707544; - ie[1].time.tv_usec = 0; - ie[1].type = 0; - ie[1].code = 0; - ie[1].value = 0; - - resetCounter(); - remove(TEST_INPUT_DEVICE); - mkfifo(TEST_INPUT_DEVICE, 0777); + ie.time.tv_sec = 946707544; + ie.time.tv_usec = 0; + ie.type = EVENT_TYPE; + ie.code = EVENT_CODE; + ie.value = EVENT_BUTTON_RELEASED; + + ::remove(TEST_INPUT_DEVICE.c_str()); + BOOST_CHECK(::mkfifo(TEST_INPUT_DEVICE.c_str(), S_IWUSR | S_IRUSR) >= 0); } - ~Fixture() { - remove(TEST_INPUT_DEVICE); - resetCounter(); + ~Fixture() + { + ::remove(TEST_INPUT_DEVICE.c_str()); } }; - } // namespace -BOOST_AUTO_TEST_CASE(Config_OK) -{ - Fixture f; +BOOST_FIXTURE_TEST_SUITE(InputMonitorSuite, Fixture) - BOOST_REQUIRE_NO_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty)); -} - -BOOST_AUTO_TEST_CASE(Config_numberOfEventsTooHigh) +BOOST_AUTO_TEST_CASE(Config_OK) { - Fixture f; - f.inputConfig.numberOfEvents = 100; - - BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty), - InputMonitorException); + BOOST_REQUIRE_NO_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback())); } BOOST_AUTO_TEST_CASE(Config_timeWindowMsTooHigh) { - Fixture f; - f.inputConfig.timeWindowMs = 50000; + inputConfig.timeWindowMs = 50000; - BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty), + BOOST_REQUIRE_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()), InputMonitorException); } BOOST_AUTO_TEST_CASE(Config_deviceFilePathNotExisting) { - Fixture f; - f.inputConfig.device = std::string(TEST_INPUT_DEVICE) + "notexisting"; + inputConfig.device = TEST_INPUT_DEVICE + "notExisting"; - BOOST_REQUIRE_THROW(InputMonitor inputMonitor(f.inputConfig, callbackEmpty), + BOOST_REQUIRE_THROW(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()), InputMonitorException); } -// ============================================================================================ -// All of the following tests are based on the (almost) the same scenario: -// 1) set up input monitor (create InputMonitor instance + register callback) -// 2) simulate an event(s) -// a) prepare an example sequence of event(s) to trigger the callback (one or more times, -// depending on the test purpose) -// b) send it to the device -// 3) verify that callback was triggered (one or more times accordingly) -// -// Button press + release events are simulated based on logs gathered from working system. -// Example log: -// -// // button pressed -// Event detected [gpio-keys.4]: -// time: 946691112.844176 sec -// type, code, value: 1, 139, 1 -// Event detected [gpio-keys.4]: -// time: 946691112.844176 sec -// type, code, value: 0, 0, 0 -// -// // button released -// Event detected [gpio-keys.4]: -// time: 946691113.384301 sec -// type, code, value: 1, 139, 0 -// Event detected [gpio-keys.4]: -// time: 946691113.384301 sec -// type, code, value: 0, 0, 0 -// -// -// ============================================================================================ - -void sendEvents(unsigned int noOfEvents) +void sendNEvents(unsigned int noOfEventsToSend) { Fixture f; Latch eventLatch; std::unique_ptr inputMonitor; - BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, - [&] { callbackCounter(); - if (getCounter() == noOfEvents) { - eventLatch.set(); - } - }))); + BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, [&] {eventLatch.set();}))); - int fd = open(TEST_INPUT_DEVICE, O_WRONLY); + int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY); + BOOST_REQUIRE(fd >= 0); - for (unsigned int i = 0; i < noOfEvents * f.inputConfig.numberOfEvents; ++i) { + for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) { // button pressed event - f.ie[0].value = EVENT_BUTTON_PRESSED; - f.ie[0].time.tv_usec += 5; - f.ie[1].time.tv_usec += 5; - ssize_t ret = safeWrite(fd, f.ie, 2 * sizeof(struct input_event)); + f.ie.value = EVENT_BUTTON_PRESSED; + f.ie.time.tv_usec += 5; + ssize_t ret = ::write(fd, &f.ie, sizeof(struct input_event)); BOOST_CHECK(ret > 0); std::this_thread::sleep_for(std::chrono::milliseconds(10)); // button released event - f.ie[0].value = EVENT_BUTTON_RELEASED; - f.ie[0].time.tv_usec += 5; - f.ie[1].time.tv_usec += 5; - ret = safeWrite(fd, f.ie, 2 * sizeof(struct input_event)); + f.ie.value = EVENT_BUTTON_RELEASED; + f.ie.time.tv_usec += 5; + ret = ::write(fd, &f.ie, sizeof(struct input_event)); BOOST_CHECK(ret > 0); } - close(fd); + BOOST_CHECK(::close(fd) >= 0); + BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend)); - BOOST_CHECK(eventLatch.wait(SINGLE_EVENT_TIMEOUT * noOfEvents)); - - // extra time to make sure, that no more events are read - std::this_thread::sleep_for(std::chrono::milliseconds(SINGLE_EVENT_TIMEOUT)); - - BOOST_CHECK(getCounter() == noOfEvents); + // Check if no more events are waiting + BOOST_CHECK(!eventLatch.wait(10)); } BOOST_AUTO_TEST_CASE(Event_oneAtATime) { - sendEvents(ONE_EVENT); + sendNEvents(1); } BOOST_AUTO_TEST_CASE(Event_tenAtATime) { - sendEvents(TEN_EVENTS); + sendNEvents(10); } -void sendEventsWithPauses(unsigned int noOfEvents) +void sendNEventsWithPauses(unsigned int noOfEventsToSend) { Fixture f; Latch eventLatch; std::unique_ptr inputMonitor; - BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, - [&] { callbackCounter(); - if (getCounter() == noOfEvents) { - eventLatch.set(); - } - }))); + BOOST_REQUIRE_NO_THROW(inputMonitor.reset(new InputMonitor(f.inputConfig, [&] {eventLatch.set();}))); - int fd = open(TEST_INPUT_DEVICE, O_WRONLY); + int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY); + BOOST_REQUIRE(fd >= 0); - for (unsigned int i = 0; i < noOfEvents * f.inputConfig.numberOfEvents; ++i) { - // button pressed event - f.ie[0].value = EVENT_BUTTON_PRESSED; - f.ie[0].time.tv_usec += 5; - f.ie[1].time.tv_usec += 5; - ssize_t ret = write(fd, f.ie, 2); // send first two bytes + for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) { + // Send first two bytes of the button pressed event + f.ie.value = EVENT_BUTTON_PRESSED; + f.ie.time.tv_usec += 5; + ssize_t ret = ::write(fd, &f.ie, 2); BOOST_CHECK(ret > 0); std::this_thread::sleep_for(std::chrono::milliseconds(10)); - ret = write(fd, &reinterpret_cast(f.ie)[2], 2 * sizeof(struct input_event) - 2); // send the remaining part + // Send the remaining part + ret = ::write(fd, &reinterpret_cast(&f.ie)[2], sizeof(struct input_event) - 2); BOOST_CHECK(ret > 0); std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // button released event - f.ie[0].value = EVENT_BUTTON_RELEASED; - f.ie[0].time.tv_usec += 5; - f.ie[1].time.tv_usec += 5; - ret = write(fd, f.ie, 2 * sizeof(struct input_event)); + // Send the button released event + f.ie.value = EVENT_BUTTON_RELEASED; + f.ie.time.tv_usec += 5; + ret = ::write(fd, &f.ie, sizeof(struct input_event)); BOOST_CHECK(ret > 0); } + BOOST_CHECK(::close(fd) >= 0); + BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend)); - close(fd); - - BOOST_CHECK(eventLatch.wait(SINGLE_EVENT_TIMEOUT * noOfEvents)); - - // extra time to make sure, that no more events are read - std::this_thread::sleep_for(std::chrono::milliseconds(SINGLE_EVENT_TIMEOUT)); - - BOOST_CHECK(getCounter() == noOfEvents); + // Check if no more events are waiting + BOOST_CHECK(!eventLatch.wait(10)); } BOOST_AUTO_TEST_CASE(Event_oneAtATimeWithPauses) { - sendEventsWithPauses(ONE_EVENT); + sendNEventsWithPauses(1); } BOOST_AUTO_TEST_CASE(Event_tenAtATimeWithPauses) { - sendEventsWithPauses(TEN_EVENTS); + sendNEventsWithPauses(10); } -- 2.7.4