#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 {
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)
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) {
: mConnection(NULL)
, mNameId(0)
{
- ScopedError error;
+ ScopedGError error;
const GDBusConnectionFlags flags =
static_cast<GDBusConnectionFlags>(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION);
const std::string& name,
GVariant* parameters)
{
- ScopedError error;
+ ScopedGError error;
g_dbus_connection_emit_signal(mConnection,
NULL,
objectPath.c_str(),
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 ||
GVariant* parameters,
const std::string& replyType)
{
- ScopedError error;
+ ScopedGError error;
GVariant* result = g_dbus_connection_call_sync(mConnection,
busName.c_str(),
objectPath.c_str(),
return ::stat(path.c_str(), &s) == 0 && S_IFCHR == (s.st_mode & S_IFMT);
}
-bool listDir(const std::string& path, std::vector<std::string>& 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";
--- /dev/null
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact: Jan Olszak <j.olszak@samsung.com>
+ *
+ * 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
--- /dev/null
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact: Jan Olszak <j.olszak@samsung.com>
+ *
+ * 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 <iostream>
+#include <gio/gio.h>
+
+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
## 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})
"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}
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
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
*
- * Contact: Pawel Broda <p.broda@partner.samsung.com>
+ * Contact: Jan Olszak <j.olszak@samsung.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @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 <cassert>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+
+#include <boost/filesystem.hpp>
+#include <boost/regex.hpp>
#include <vector>
+#include <functional>
+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<std::string> 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<InputMonitor*>(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<ReadDeviceCallback>);
+ 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<ReadDeviceCallback>(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<gchar*>(&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<unsigned int>(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<long int>((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
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
*
- * Contact: Pawel Broda <p.broda@partner.samsung.com>
+ * Contact: Jan Olszak <j.olszak@samsung.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @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 <functional>
-#include <glib.h>
#include <linux/input.h>
#include <sys/time.h>
+#include <glib.h>
+
+#include <functional>
+#include <string>
+#include <list>
namespace security_containers {
-
class InputMonitor {
public:
- typedef std::function<void()> InputNotifyCallback;
- InputMonitor(const InputConfig&, const InputNotifyCallback&);
+ typedef std::function<void()> 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<void(GIOChannel* gio)> ReadDeviceCallback;
+
InputConfig mConfig;
+ NotifyCallback mNotifyCallback;
+
+ std::list<struct timeval> 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
## 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})
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
*
- * Contact: Pawel Broda <p.broda@partner.samsung.com>
+ * Contact: Jan Olszak <j.olszak@samsung.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
/**
* @file
- * @author Pawel Broda (p.broda@partner.samsung.com)
+ * @author Jan Olszak (j.olszak@samsung.com)
* @brief Unit tests of the InputMonitor class
*/
#include "utils/glib-loop.hpp"
#include "utils/latch.hpp"
-#include <atomic>
-#include <chrono>
-#include <fcntl.h>
-#include <memory>
-#include <string>
-#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <thread>
+#include <fcntl.h>
+#include <stdio.h>
#include <unistd.h>
+#include <boost/filesystem.hpp>
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <string>
-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<unsigned int> 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;
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> 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> 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<char *>(f.ie)[2], 2 * sizeof(struct input_event) - 2); // send the remaining part
+ // Send the remaining part
+ ret = ::write(fd, &reinterpret_cast<char*>(&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);
}