Import RMI code for internalize
authorSangwan Kwon <sangwan.kwon@samsung.com>
Thu, 28 Nov 2019 06:20:13 +0000 (15:20 +0900)
committer권상완/Security 2Lab(SR)/Engineer/삼성전자 <sangwan.kwon@samsung.com>
Mon, 2 Dec 2019 06:36:38 +0000 (15:36 +0900)
Signed-off-by: Sangwan Kwon <sangwan.kwon@samsung.com>
25 files changed:
src/vist/CMakeLists.txt
src/vist/event/CMakeLists.txt [new file with mode: 0644]
src/vist/event/eventfd.cpp [new file with mode: 0644]
src/vist/event/eventfd.hpp [new file with mode: 0644]
src/vist/event/mainloop.cpp [new file with mode: 0644]
src/vist/event/mainloop.hpp [new file with mode: 0644]
src/vist/klass/CMakeLists.txt [new file with mode: 0644]
src/vist/klass/function.hpp [new file with mode: 0644]
src/vist/klass/functor.hpp [new file with mode: 0644]
src/vist/klass/tests/functor.cpp [new file with mode: 0644]
src/vist/rmi/CMakeLists.txt [new file with mode: 0644]
src/vist/rmi/client.cpp [new file with mode: 0644]
src/vist/rmi/client.hpp [new file with mode: 0644]
src/vist/rmi/server.cpp [new file with mode: 0644]
src/vist/rmi/server.hpp [new file with mode: 0644]
src/vist/rmi/tests/server-client.cpp [new file with mode: 0644]
src/vist/transport/CMakeLists.txt [new file with mode: 0644]
src/vist/transport/connection.cpp [new file with mode: 0644]
src/vist/transport/connection.hpp [new file with mode: 0644]
src/vist/transport/message.cpp [new file with mode: 0644]
src/vist/transport/message.hpp [new file with mode: 0644]
src/vist/transport/socket.cpp [new file with mode: 0644]
src/vist/transport/socket.hpp [new file with mode: 0644]
src/vist/transport/tests/connection.cpp [new file with mode: 0644]
src/vist/transport/tests/socket.cpp [new file with mode: 0644]

index 63162e19634b03716495ffa4d8c2718f447f5169..25ec60199e8c596bb1e7afaaac432cdb58f59ab4 100644 (file)
@@ -33,9 +33,13 @@ ADD_DEFINITIONS(-DDB_PATH="${DB_INSTALL_DIR}/.vist.db"
 
 # common
 ADD_SUBDIRECTORY(common)
+ADD_SUBDIRECTORY(event)
 ADD_SUBDIRECTORY(ipc)
+ADD_SUBDIRECTORY(klass)
 ADD_SUBDIRECTORY(logger)
+ADD_SUBDIRECTORY(rmi)
 ADD_SUBDIRECTORY(sdk)
+ADD_SUBDIRECTORY(transport)
 
 # policy
 ADD_SUBDIRECTORY(policy)
diff --git a/src/vist/event/CMakeLists.txt b/src/vist/event/CMakeLists.txt
new file mode 100644 (file)
index 0000000..25dd007
--- /dev/null
@@ -0,0 +1,16 @@
+#  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_VIST_COMMON_LIBRARY(vist_event eventfd.cpp
+                                                                  mainloop.cpp)
diff --git a/src/vist/event/eventfd.cpp b/src/vist/event/eventfd.cpp
new file mode 100644 (file)
index 0000000..3e27692
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) 2018-present 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   eventfd.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of event notification.
+ */
+
+#include "eventfd.hpp"
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cstdint>
+#include <stdexcept>
+
+namespace vist {
+namespace event {
+
+EventFD::EventFD(unsigned int initval, int flags)
+       : fd(::eventfd(initval, flags))
+{
+       if (this->fd == -1)
+               throw std::runtime_error("Failed to create eventfd.");
+}
+
+EventFD::~EventFD()
+{
+       ::close(fd);
+}
+
+void EventFD::send(void)
+{
+       const std::uint64_t val = 1;
+       if (::write(this->fd, &val, sizeof(val)) == -1)
+               throw std::runtime_error("Failed to write to eventfd.");
+}
+
+void EventFD::receive(void)
+{
+       std::uint64_t val;
+       if (::read(this->fd, &val, sizeof(val)) == -1)
+               throw std::runtime_error("Failed to read from eventfd.");
+}
+
+int EventFD::getFd(void) const noexcept
+{
+       return this->fd;
+}
+
+} // namespace event
+} // namespace vist
diff --git a/src/vist/event/eventfd.hpp b/src/vist/event/eventfd.hpp
new file mode 100644 (file)
index 0000000..6b85707
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (c) 2018-present 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   eventfd.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  A file descriptor for event notification.
+ */
+
+#pragma once
+
+#include <sys/eventfd.h>
+
+namespace vist {
+namespace event {
+
+class EventFD final {
+public:
+       explicit EventFD(unsigned int initval = 0, int flags = EFD_SEMAPHORE | EFD_CLOEXEC);
+       ~EventFD();
+
+       EventFD(const EventFD&) = delete;
+       EventFD& operator=(const EventFD&) = delete;
+
+       EventFD(EventFD&&) = delete;
+       EventFD& operator=(EventFD&&) = delete;
+
+       void send(void);
+       void receive(void);
+
+       int getFd(void) const noexcept;
+
+private:
+       int fd;
+};
+
+} // namespace event
+} // namespace vist
diff --git a/src/vist/event/mainloop.cpp b/src/vist/event/mainloop.cpp
new file mode 100644 (file)
index 0000000..991ceb8
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ *  Copyright (c) 2018-present 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   mainloop.cpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of mainloop.
+ */
+
+#include "mainloop.hpp"
+
+#include <vist/logger.hpp>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <cstring>
+
+namespace vist {
+namespace event {
+
+Mainloop::Mainloop() :
+       epollFd(::epoll_create1(EPOLL_CLOEXEC)),
+       stopped(false)
+{
+       if (epollFd == -1)
+               throw std::runtime_error("Failed to create epoll instance.");
+}
+
+Mainloop::~Mainloop()
+{
+       ::close(this->epollFd);
+}
+
+void Mainloop::addHandler(const int fd, OnEvent&& onEvent, OnError&& onError)
+{
+       std::lock_guard<Mutex> lock(mutex);
+
+       if (this->listener.find(fd) != this->listener.end())
+               throw std::runtime_error("Event is already registered.");
+
+       ::epoll_event event;
+       std::memset(&event, 0, sizeof(epoll_event));
+
+       event.events = EPOLLIN | EPOLLHUP | EPOLLRDHUP;
+       event.data.fd = fd;
+
+       if (::epoll_ctl(this->epollFd, EPOLL_CTL_ADD, fd, &event) == -1)
+               throw std::runtime_error("Failed to add event source.");
+
+       auto onEventPtr = std::make_shared<OnEvent>(onEvent);
+       auto onErrorPtr = (onError != nullptr) ? std::make_shared<OnError>(onError) : nullptr;
+
+       auto handler = std::make_pair(std::move(onEventPtr), std::move(onErrorPtr));
+
+       this->listener.insert({fd, handler});
+}
+
+void Mainloop::removeHandler(const int fd)
+{
+       std::lock_guard<Mutex> lock(mutex);
+
+       auto iter = this->listener.find(fd);
+       if (iter == this->listener.end())
+               return;
+
+       this->listener.erase(iter);
+
+       ::epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, NULL);
+}
+
+bool Mainloop::prepare(void)
+{
+       auto wakeup = [this]() {
+               this->wakeupSignal.receive();
+               this->removeHandler(wakeupSignal.getFd());
+               this->stopped = true;
+       };
+
+       this->addHandler(this->wakeupSignal.getFd(), wakeup);
+}
+
+bool Mainloop::dispatch(int timeout) noexcept
+{
+       int nfds;
+       ::epoll_event event[MAX_EPOLL_EVENTS];
+
+       do {
+               errno = 0;
+               nfds = ::epoll_wait(epollFd, event, MAX_EPOLL_EVENTS, timeout);
+       } while ((nfds == -1) && (errno == EINTR));
+
+       if (nfds <= 0)
+               return false;
+
+       for (int i = 0; i < nfds; i++) {
+               std::shared_ptr<OnEvent> onEvent;
+               std::shared_ptr<OnError> onError;
+
+               {
+                       std::lock_guard<Mutex> lock(mutex);
+
+                       auto iter = this->listener.find(event[i].data.fd);
+                       if (iter == this->listener.end())
+                               continue;
+
+                       onEvent = iter->second.first;
+                       onError = iter->second.second;
+               }
+
+               try {
+                       if ((event[i].events & (EPOLLHUP | EPOLLRDHUP))) {
+                               if (onError != nullptr)
+                                       (*onError)();
+                       } else {
+                               (*onEvent)();
+                       }
+
+               } catch (std::exception& e) {
+                       ERROR(VIST) << "EXCEPTION ON MAINLOOP: " << e.what();
+               }
+       }
+
+       return true;
+}
+
+void Mainloop::run(int timeout)
+{
+       bool done = false;
+       this->stopped = false;
+
+       this->prepare();
+
+       while (!this->stopped && !done) {
+               done = !dispatch(timeout);
+       }
+}
+
+void Mainloop::stop(void)
+{
+       this->wakeupSignal.send();
+}
+
+} // namespace event
+} // namespace vist
diff --git a/src/vist/event/mainloop.hpp b/src/vist/event/mainloop.hpp
new file mode 100644 (file)
index 0000000..6e2f1ca
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ *  Copyright (c) 2018-present 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   mainloop.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  The loop for events.
+ */
+
+#pragma once
+
+#include <vist/event/eventfd.hpp>
+
+#include <sys/epoll.h>
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <stdexcept>
+#include <string>
+#include <unordered_map>
+
+namespace vist {
+namespace event {
+
+class Mainloop {
+public:
+       using OnEvent = std::function<void(void)>;
+       using OnError = std::function<void(void)>;
+
+       Mainloop();
+       virtual ~Mainloop();
+
+       Mainloop(const Mainloop&) = delete;
+       Mainloop& operator=(const Mainloop&) = delete;
+
+       Mainloop(Mainloop&&) = delete;
+       Mainloop& operator=(Mainloop&&) = delete;
+
+       void addHandler(const int fd, OnEvent&& onEvent, OnError&& = nullptr);
+       void removeHandler(const int fd);
+
+       void run(int timeout = -1);
+       void stop(void);
+
+private:
+       // recursive_mutex makes additional calls to lock in calling thread.
+       // And other threads will block (for calls to lock).
+       // So, addHandler() can be called during dispatch().
+       using Mutex = std::recursive_mutex;
+       using Handler = std::pair<std::shared_ptr<OnEvent>, std::shared_ptr<OnError>>;
+       using Listener = std::unordered_map<int, Handler>;
+
+       bool prepare(void);
+
+       bool dispatch(const int timeout) noexcept;
+
+       Mutex mutex;
+       Listener listener;
+       EventFD wakeupSignal;
+
+       int epollFd;
+       std::atomic<bool> stopped;
+
+       const int MAX_EPOLL_EVENTS = 16;
+};
+
+} // namespace event
+} // namespace vist
diff --git a/src/vist/klass/CMakeLists.txt b/src/vist/klass/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fea5e7e
--- /dev/null
@@ -0,0 +1,16 @@
+#  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(GLOB KLASS_TESTS "tests/*.cpp")
+ADD_VIST_TEST(${KLASS_TESTS})
diff --git a/src/vist/klass/function.hpp b/src/vist/klass/function.hpp
new file mode 100644 (file)
index 0000000..db6b35b
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  Copyright (c) 2018-present 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   function.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Hold the class member fuction.
+ */
+
+#pragma once
+
+#include <type_traits>
+
+namespace vist {
+namespace klass {
+
+template<typename T>
+using remove_cv_t = typename std::remove_cv<T>::type;
+template<typename T>
+using remove_ref_t = typename std::remove_reference<T>::type;
+template<typename T>
+using remove_cv_ref_t = remove_cv_t<remove_ref_t<T>>;
+
+template<typename R, typename K, typename... Ps>
+class Function {
+public:
+       using Klass = K;
+       using Return = R;
+       using Parameters = std::tuple<remove_cv_ref_t<Ps>...>;
+       using Pointer = Return (Klass::*)(Ps...);
+
+       auto get(void) noexcept -> const Pointer&;
+
+private:
+       explicit Function(Pointer pointer);
+
+       template<typename RR, typename KK, typename... PPs>
+       friend Function<RR, KK, PPs...> make_function(RR (KK::* member)(PPs...));
+
+       Pointer pointer;
+};
+
+template<typename R, typename K, typename... Ps>
+Function<R, K, Ps...>::Function(Pointer pointer) : pointer(pointer)
+{
+}
+
+template<typename R, typename K, typename... Ps>
+auto Function<R, K, Ps...>::get(void) noexcept -> const Pointer&
+{
+       return this->pointer;
+}
+
+template<typename R, typename K, typename... Ps>
+Function<R, K, Ps...> make_function(R (K::* member)(Ps...))
+{
+       constexpr bool notVoid = !(std::is_same<R, void>::value);
+       static_assert(notVoid, "Return type cannot be void.");
+
+       using IsValid = std::is_member_function_pointer<decltype(member)>;
+       static_assert(IsValid::value, "Pamameter should be member function type.");
+
+       return Function<R, K, Ps...>(member);
+}
+
+} // namespace klass
+} // namespace vist
diff --git a/src/vist/klass/functor.hpp b/src/vist/klass/functor.hpp
new file mode 100644 (file)
index 0000000..7a178ad
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ *  Copyright (c) 2018-present 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   functor.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Functor is callable object which binds instance with member function.
+ */
+
+#pragma once
+
+#include <vist/archive.hpp>
+#include <vist/klass/function.hpp>
+
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <unordered_map>
+
+namespace vist {
+namespace klass {
+
+struct AbstractFunctor {
+       template<typename R, typename...Args>
+       R invoke(Args&&... args);
+       inline Archive invoke(Archive& archive);
+
+protected:
+       virtual Archive dispatch(Archive& archive) = 0;
+};
+
+using FunctorMap = std::unordered_map<std::string, std::shared_ptr<AbstractFunctor>>;
+
+template<typename R, typename K, typename... Ps>
+class Functor : public AbstractFunctor {
+public:
+       using Klass = K;
+       using MemFunc = Function<R, K, Ps...>;
+       using Invokable = std::function<R(K&, Ps...)>;
+
+       explicit Functor(std::shared_ptr<Klass> instance, MemFunc memFunc);
+
+       template<typename... Args>
+       auto operator()(Args&&... args) -> typename MemFunc::Return;
+       inline auto operator()(Archive& archive) -> typename MemFunc::Return;
+
+protected:
+       inline Archive dispatch(Archive& archive) override;
+
+private:
+       template<typename T>
+       auto operator()(T& tuple, EmptySequence) -> typename MemFunc::Return;
+       template<typename T, std::size_t... I>
+       auto operator()(T& tuple, IndexSequence<I...>) -> typename MemFunc::Return;
+
+       std::shared_ptr<Klass> instance;
+       MemFunc memFunc;
+};
+
+template<typename R, typename...Args>
+R AbstractFunctor::invoke(Args&&... args)
+{
+       Archive parameters;
+       parameters.pack(std::forward<Args>(args)...);
+
+       auto result = this->dispatch(parameters);
+       R ret;
+       result >> ret;
+       return ret;
+}
+
+Archive AbstractFunctor::invoke(Archive& archive)
+{
+       return this->dispatch(archive);
+}
+
+template<typename R, typename K, typename... Ps>
+Functor<R, K, Ps...>::Functor(std::shared_ptr<Klass> instance, MemFunc memFunc)
+       : instance(instance), memFunc(std::move(memFunc))
+{
+}
+
+template<typename R, typename K, typename... Ps>
+template<typename... Args>
+auto Functor<R, K, Ps...>::operator()(Args&&... args) -> typename MemFunc::Return
+{
+       const Invokable& invokable = this->memFunc.get();
+       return invokable(*this->instance, std::forward<Args>(args)...);
+}
+
+template<typename R, typename K, typename... Ps>
+auto Functor<R, K, Ps...>::operator()(Archive& archive) -> typename MemFunc::Return
+{
+       using ParamsTuple = typename MemFunc::Parameters;
+       constexpr auto size = std::tuple_size<ParamsTuple>::value;
+
+       ParamsTuple params;
+       archive.transform(params);
+
+       return (*this)(params, make_index_sequence<size>());
+}
+
+template<typename R, typename K, typename... Ps>
+Archive Functor<R, K, Ps...>::dispatch(Archive& archive)
+{
+       Archive ret;
+       return (ret << (*this)(archive));
+}
+
+template<typename R, typename K, typename... Ps>
+template<typename T>
+auto Functor<R, K, Ps...>::operator()(T& tuple,
+                                                                         EmptySequence) -> typename MemFunc::Return
+{
+       return (*this)();
+}
+
+template<typename R, typename K, typename... Ps>
+template<typename T, std::size_t... I>
+auto Functor<R, K, Ps...>::operator()(T& tuple,
+                                                                         IndexSequence<I...>) -> typename MemFunc::Return
+{
+       return (*this)(std::get<I>(tuple)...);
+}
+
+template<typename R, typename K, typename... Ps>
+Functor<R, K, Ps...> make_functor(std::shared_ptr<K> instance, R (K::* member)(Ps...))
+{
+       if (instance == nullptr)
+               throw std::invalid_argument("Instance can't be nullptr.");
+
+       return Functor<R, K, Ps...>(instance, make_function(member));
+}
+
+template<typename R, typename K, typename... Ps>
+std::shared_ptr<Functor<R, K, Ps...>> make_functor_ptr(std::shared_ptr<K> instance,
+                                                                                                          R (K::* member)(Ps...))
+{
+       if (instance == nullptr)
+               throw std::invalid_argument("Instance can't be nullptr.");
+
+       return std::make_shared<Functor<R, K, Ps...>>(instance, make_function(member));
+}
+
+} // namespace klass
+} // namespace vist
diff --git a/src/vist/klass/tests/functor.cpp b/src/vist/klass/tests/functor.cpp
new file mode 100644 (file)
index 0000000..91b0360
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ *  Copyright (c) 2018-present 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 <vist/archive.hpp>
+#include <vist/klass/functor.hpp>
+
+#include <iostream>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+using namespace vist;
+using namespace vist::klass;
+
+struct Foo {
+       bool setName(const std::string& name)
+       {
+               this->name = name;
+               return false;
+       }
+
+       std::string getName(void)
+       {
+               return this->name;
+       }
+
+       int echo(const std::string& a, const std::string b, std::string& c)
+       {
+               std::cout << a << ", " << b << ", " << c << std::endl;
+               return false;
+       }
+
+//     void impossible(void) {}
+
+       std::string name;
+};
+
+TEST(FunctorTests, functor)
+{
+       auto foo = std::make_shared<Foo>();
+       auto fooSetName = make_functor(foo, &Foo::setName);
+       auto fooGetName = make_functor(foo, &Foo::getName);
+       auto fooEcho = make_functor(foo, &Foo::echo);
+//     auto fooImp = make_functor(foo, &Foo::impossible);
+
+       std::string input = "Foo name";
+       bool ret = true;
+       ret = fooSetName(input);
+       EXPECT_EQ(ret, false);
+
+       std::string output = fooGetName();
+       EXPECT_EQ(output, input);
+
+       std::string a("aaa"), b("bbb"), c("ccc");
+       ret = true;
+       ret = fooEcho(a, b, c);
+       EXPECT_EQ(ret, false);
+}
+
+TEST(FunctorTests, functor_map)
+{
+       auto foo = std::make_shared<Foo>();
+
+       FunctorMap fooMap;
+       fooMap["setName"] = make_functor_ptr(foo, &Foo::setName);
+       fooMap["getName"] = make_functor_ptr(foo, &Foo::getName);
+       fooMap["echo"] = make_functor_ptr(foo, &Foo::echo);
+
+       auto fooSetNamePtr = fooMap.at("setName");
+       auto fooGetNamePtr = fooMap.at("getName");
+       auto fooEchoPtr = fooMap.at("echo");
+
+       std::string input = "Foo name";
+       bool ret = true;
+       ret = fooSetNamePtr->invoke<bool>(input);
+       EXPECT_EQ(ret, false);
+
+       std::string output = fooGetNamePtr->invoke<std::string>();
+       EXPECT_EQ(output, input);
+
+       std::string a("aaaa"), b("bbbb"), c("cccc");
+       ret = true;
+       ret = fooEchoPtr->invoke<bool>(a, b, c);
+       EXPECT_EQ(ret, false);
+}
+
+TEST(FunctorTests, archive)
+{
+       auto foo = std::make_shared<Foo>();
+
+       FunctorMap fooMap;
+       fooMap["echo"] = make_functor_ptr(foo, &Foo::echo);
+
+       std::string a("aaaa"), b("bbbb"), c("cccc");
+       Archive archive;
+       archive << a << b << c;
+
+       auto fooEchoPtr = fooMap.at("echo");
+       auto result = fooEchoPtr->invoke(archive);
+       bool ret = true;
+       result >> ret;
+       EXPECT_EQ(ret, false);
+}
diff --git a/src/vist/rmi/CMakeLists.txt b/src/vist/rmi/CMakeLists.txt
new file mode 100644 (file)
index 0000000..167ef87
--- /dev/null
@@ -0,0 +1,19 @@
+#  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_VIST_COMMON_LIBRARY(vist_rmi client.cpp
+                                                                server.cpp)
+
+FILE(GLOB RMI_TESTS "tests/*.cpp")
+ADD_VIST_TEST(${RMI_TESTS})
diff --git a/src/vist/rmi/client.cpp b/src/vist/rmi/client.cpp
new file mode 100644 (file)
index 0000000..1d289f4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  Copyright (c) 2018-present 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   client.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of client application.
+ */
+
+#include "client.hpp"
+
+namespace vist {
+namespace rmi {
+
+Client::Client(const std::string& remotePath) : connection(remotePath)
+{
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/client.hpp b/src/vist/rmi/client.hpp
new file mode 100644 (file)
index 0000000..fec6ed6
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  Copyright (c) 2018-present 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   client.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Client application for invoking remote function(method).
+ */
+
+#pragma once
+
+#include <vist/transport/connection.hpp>
+#include <vist/transport/message.hpp>
+
+#include <string>
+#include <mutex>
+
+using namespace vist::transport;
+
+namespace vist {
+namespace rmi {
+
+class Client {
+public:
+       explicit Client(const std::string& remotePath);
+       virtual ~Client() = default;
+
+       Client(const Client&) = delete;
+       Client& operator=(const Client&) = delete;
+
+       Client(Client&&) = delete;
+       Client& operator=(Client&&) = delete;
+
+       template<typename R, typename... Args>
+       R invoke(const std::string& name, Args&&... args);
+
+private:
+       Connection connection;
+       std::mutex mutex;
+};
+
+template<typename R, typename... Args>
+R Client::invoke(const std::string& name, Args&&... args)
+{
+       Message msg(Message::Type::MethodCall, name);
+       msg.enclose(std::forward<Args>(args)...);
+
+       std::lock_guard<std::mutex> lock(this->mutex);
+
+       Message reply = this->connection.request(msg);
+       R ret;
+       reply.disclose(ret);
+
+       return ret;
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/server.cpp b/src/vist/rmi/server.cpp
new file mode 100644 (file)
index 0000000..269fa9c
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ *  Copyright (c) 2018-present 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   server.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of server application.
+ */
+
+#include "server.hpp"
+
+#include <vist/logger.hpp>
+#include <vist/transport/message.hpp>
+
+namespace vist {
+namespace rmi {
+
+void Server::start(void)
+{
+       for (const auto& path : this->socketPaths) {
+               auto socket = std::make_shared<Socket>(path);
+               auto accept = [this, socket]() {
+                       this->onAccept(std::make_shared<Connection>(socket->accept()));
+               };
+
+               this->mainloop.addHandler(socket->getFd(), std::move(accept));
+       }
+
+       this->mainloop.run();
+}
+
+void Server::stop(void)
+{
+       {
+               std::lock_guard<std::mutex> lock(this->connectionMutex);
+
+               for (auto iter : this->connectionMap)
+                       this->mainloop.removeHandler(iter.first);
+       }
+
+       this->mainloop.stop();
+}
+
+void Server::listen(const std::string& socketPath)
+{
+       this->socketPaths.insert(socketPath);
+}
+
+void Server::onAccept(std::shared_ptr<Connection>&& connection)
+{
+       if (connection == nullptr)
+               throw std::invalid_argument("Wrong connection.");
+
+       auto onRead = [this, connection]() {
+               std::shared_ptr<Connection> conn;
+
+               std::lock_guard<std::mutex> lock(this->connectionMutex);
+
+               auto iter = this->connectionMap.find(connection->getFd());
+               if (iter == this->connectionMap.end())
+                       throw std::runtime_error("Faild to find connection.");
+
+               conn = iter->second;
+
+               this->dispatch(conn);
+       };
+
+       auto onError = [this, connection]() {
+               ERROR(VIST) << "Connection error occured. fd: " << connection->getFd();
+               this->onClose(connection);
+       };
+
+       int clientFd = connection->getFd();
+       this->mainloop.addHandler(clientFd, std::move(onRead), std::move(onError));
+       INFO(VIST) << "Connection is accepted. fd: " << clientFd;
+
+       {
+               std::lock_guard<std::mutex> lock(this->connectionMutex);
+
+               this->dispatch(connection);
+               this->connectionMap[clientFd] = std::move(connection);
+       }
+}
+
+void Server::onClose(const std::shared_ptr<Connection>& connection)
+{
+       if (connection == nullptr)
+               throw std::invalid_argument("Wrong connection.");
+
+       {
+               std::lock_guard<std::mutex> lock(this->connectionMutex);
+
+               auto iter = this->connectionMap.find(connection->getFd());
+               if (iter == this->connectionMap.end())
+                       throw std::runtime_error("Faild to find connection.");
+
+               this->mainloop.removeHandler(iter->first);
+               INFO(VIST) << "Connection is closed. fd: " << iter->first;
+               this->connectionMap.erase(iter);
+       }
+}
+
+void Server::dispatch(const std::shared_ptr<Connection>& connection)
+{
+       Message request = connection->recv();
+       std::string funcName = request.signature;
+
+       {
+               std::lock_guard<std::mutex> lock(this->functorMutex);
+
+               auto iter = this->functorMap.find(funcName);
+               if (iter == this->functorMap.end())
+                       throw std::runtime_error("Faild to find function.");
+
+               DEBUG(VIST) << "Remote method invokation: " << funcName;
+
+               auto functor = iter->second;
+               auto result = functor->invoke(request.buffer);
+
+               Message reply(Message::Type::Reply, funcName);
+               reply.enclose(result);
+
+               connection->send(reply);
+       }
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/server.hpp b/src/vist/rmi/server.hpp
new file mode 100644 (file)
index 0000000..d18fa92
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (c) 2018-present 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   server.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Server application for exposing function(method).
+ */
+
+#pragma once
+
+#include <vist/event/mainloop.hpp>
+#include <vist/klass/functor.hpp>
+#include <vist/transport/connection.hpp>
+#include <vist/transport/socket.hpp>
+
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <mutex>
+#include <memory>
+
+using namespace vist::klass;
+using namespace vist::transport;
+using namespace vist::event;
+
+namespace vist {
+namespace rmi {
+
+class Server {
+public:
+       explicit Server() = default;
+       virtual ~Server() = default;
+
+       Server(const Server&) = delete;
+       Server& operator=(const Server&) = delete;
+
+       Server(Server&&) = delete;
+       Server& operator=(Server&&) = delete;
+
+       void start(void);
+       void stop(void);
+
+       void listen(const std::string& socketPath);
+
+       template<typename O, typename F>
+       void expose(O&& object, const std::string& name, F&& func);
+
+private:
+       using ConnectionMap = std::unordered_map<int, std::shared_ptr<Connection>>;
+
+       void onAccept(std::shared_ptr<Connection>&& connection);
+       void onClose(const std::shared_ptr<Connection>& connection);
+
+       void dispatch(const std::shared_ptr<Connection>& connection);
+
+       Mainloop mainloop;
+
+       std::set<std::string> socketPaths;
+
+       ConnectionMap connectionMap;
+       std::mutex connectionMutex;
+
+       FunctorMap functorMap;
+       std::mutex functorMutex;
+};
+
+template<typename O, typename F>
+void Server::expose(O&& object, const std::string& name, F&& func)
+{
+       auto functor = make_functor_ptr(std::forward<O>(object), std::forward<F>(func));
+       this->functorMap[name] = functor;
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/tests/server-client.cpp b/src/vist/rmi/tests/server-client.cpp
new file mode 100644 (file)
index 0000000..6a18cfa
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  Copyright (c) 2018-present 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 <vist/rmi/server.hpp>
+#include <vist/rmi/client.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+using namespace vist::rmi;
+using namespace vist::transport;
+
+// Server side methods
+struct Foo {
+       bool setName(const std::string& name)
+       {
+               this->name = name;
+               return false;
+       }
+
+       std::string getName(void)
+       {
+               return this->name;
+       }
+
+       std::string name;
+};
+
+TEST(RmiTests, server_client)
+{
+       std::string sockPath = ("/tmp/test-server");
+
+       // server-side
+       Server server;
+       server.listen(sockPath);
+
+       auto foo = std::make_shared<Foo>();
+       server.expose(foo, "Foo::setName", &Foo::setName);
+       server.expose(foo, "Foo::getName", &Foo::getName);
+
+       auto client = std::thread([&]() {
+               std::this_thread::sleep_for(std::chrono::seconds(1));
+
+               // client-side
+               Client client(sockPath);
+
+               std::string param = "RMI-TEST";
+               bool ret = client.invoke<bool>("Foo::setName", param);
+               EXPECT_EQ(ret, false);
+
+               std::string name = client.invoke<std::string>("Foo::getName");
+               EXPECT_EQ(name, param);
+
+               server.stop();
+       });
+
+       server.start();
+
+       if (client.joinable())
+               client.join();
+}
diff --git a/src/vist/transport/CMakeLists.txt b/src/vist/transport/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b3fb7b1
--- /dev/null
@@ -0,0 +1,20 @@
+#  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_VIST_COMMON_LIBRARY(vist_transport connection.cpp
+                                                                          socket.cpp
+                                                                          message.cpp)
+
+FILE(GLOB TRANSPORT_TESTS "tests/*.cpp")
+ADD_VIST_TEST(${TRANSPORT_TESTS})
diff --git a/src/vist/transport/connection.cpp b/src/vist/transport/connection.cpp
new file mode 100644 (file)
index 0000000..b9fdbe7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ *  Copyright (c) 2018-present 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   connection.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of the socket communication session.
+ */
+
+#include "connection.hpp"
+
+#include <utility>
+
+namespace vist {
+namespace transport {
+
+Connection::Connection(transport::Socket&& socket) noexcept : socket(std::move(socket))
+{
+}
+
+Connection::Connection(const std::string& path) :
+       socket(transport::Socket::connect(path))
+{
+}
+
+void Connection::send(Message& message)
+{
+       std::lock_guard<std::mutex> lock(this->sendMutex);
+
+       message.header.id = this->sequence++;
+       this->socket.send(&message.header);
+
+       this->socket.send(message.buffer.get(), message.header.length);
+}
+
+Message Connection::recv(void) const
+{
+       std::lock_guard<std::mutex> lock(this->recvMutex);
+       Message::Header header;
+       this->socket.recv(&header);
+
+       Message message(header);
+       this->socket.recv(message.buffer.get(), message.size());
+       message.disclose(message.signature);
+
+       return message;
+}
+
+Message Connection::request(Message& message)
+{
+       this->send(message);
+       return this->recv();
+}
+
+int Connection::getFd(void) const noexcept
+{
+       return this->socket.getFd();
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/connection.hpp b/src/vist/transport/connection.hpp
new file mode 100644 (file)
index 0000000..4710146
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) 2018-present 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   connection.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Define the socket communication session.
+ */
+
+#pragma once
+
+#include <vist/transport/message.hpp>
+#include <vist/transport/socket.hpp>
+
+#include <mutex>
+#include <utility>
+
+namespace vist {
+namespace transport {
+
+class Connection {
+public:
+       explicit Connection(transport::Socket&& socket) noexcept;
+       explicit Connection(const std::string& path);
+       virtual ~Connection() = default;
+
+       Connection(const Connection&) = delete;
+       Connection& operator=(const Connection&) = delete;
+
+       Connection(Connection&&) = default;
+       Connection& operator=(Connection&&) = default;
+
+       // server-side
+       void send(Message& message);
+       Message recv(void) const;
+
+       // client-side
+       Message request(Message& message);
+
+       int getFd(void) const noexcept;
+
+private:
+       transport::Socket socket;
+
+       // SOCK_STREAM are full-duplex byte streams
+       mutable std::mutex sendMutex;
+       mutable std::mutex recvMutex;
+
+       unsigned int sequence = 0;
+};
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/message.cpp b/src/vist/transport/message.cpp
new file mode 100644 (file)
index 0000000..aca58d6
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  Copyright (c) 2018-present 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   message.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementaion of message.
+ */
+
+#include "message.hpp"
+
+namespace vist {
+namespace transport {
+
+Message::Message(unsigned int type, const std::string& signature) :
+       header({0, type, signature.size()}),
+       signature(signature)
+{
+       this->enclose(signature);
+}
+
+Message::Message(Header header) : header(header)
+{
+       this->buffer.reserve(this->header.length);
+}
+
+std::size_t Message::size(void) const noexcept
+{
+       return this->header.length;
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/message.hpp b/src/vist/transport/message.hpp
new file mode 100644 (file)
index 0000000..f8c832b
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  Copyright (c) 2018-present 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   message.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  The unit of socket communication.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <vist/archive.hpp>
+
+namespace vist {
+namespace transport {
+
+struct Message final {
+       enum Type : unsigned int {
+               Invalid,
+               MethodCall,
+               Reply,
+               Error,
+               Signal
+       };
+
+       struct Header {
+               unsigned int id;
+               unsigned int type;
+               size_t length;
+       };
+
+       explicit Message(void) = default;
+       explicit Message(unsigned int type, const std::string& signature);
+       explicit Message(Header header);
+
+       ~Message(void) noexcept = default;
+
+       Message(const Message&) = default;
+       Message(Message&&) = default;
+
+       Message& operator=(const Message&) = default;
+       Message& operator=(Message&&) = default;
+
+       template<typename... Args>
+       void enclose(Args&&... args);
+       template<typename... Args>
+       void disclose(Args&... args);
+
+       std::size_t size(void) const noexcept;
+
+       Header header;
+       std::string signature;
+       Archive buffer;
+};
+
+template<typename... Args>
+void Message::enclose(Args&&... args)
+{
+       this->buffer.pack(std::forward<Args>(args)...);
+       header.length = this->buffer.size();
+}
+
+template<typename... Args>
+void Message::disclose(Args&... args)
+{
+       this->buffer.unpack(args...);
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/socket.cpp b/src/vist/transport/socket.cpp
new file mode 100644 (file)
index 0000000..cb7bbc4
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (c) 2018-present 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   socket.cpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Implementation of Unix Domain Socket.
+ */
+
+#include "socket.hpp"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <fstream>
+#include <iostream>
+
+namespace vist {
+namespace transport {
+
+namespace {
+
+void set_cloexec(int fd)
+{
+       if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+               throw std::runtime_error("Failed to set CLOSEXEC.");
+}
+
+} // anonymous namespace
+
+Socket::Socket(int fd) noexcept : fd(fd)
+{
+}
+
+Socket::Socket(const std::string& path)
+{
+       if (path.size() >= sizeof(::sockaddr_un::sun_path))
+               throw std::invalid_argument("Socket path size is wrong.");
+
+       int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd == -1)
+               throw std::runtime_error("Failed to create socket.");
+
+       set_cloexec(fd);
+
+       ::sockaddr_un addr;
+       addr.sun_family = AF_UNIX;
+       ::strncpy(addr.sun_path, path.c_str(), sizeof(sockaddr_un::sun_path));
+
+       if (addr.sun_path[0] == '@')
+               addr.sun_path[0] = '\0';
+
+       struct stat buf;
+       if (::stat(path.c_str(), &buf) == 0)
+               if (::unlink(path.c_str()) == -1)
+                       throw std::runtime_error("Failed to remove exist socket.");
+
+       if (::bind(fd, reinterpret_cast<::sockaddr*>(&addr), sizeof(::sockaddr_un)) == -1) {
+               ::close(fd);
+               throw std::runtime_error("Failed to bind.");
+       }
+
+       if (::listen(fd, MAX_BACKLOG_SIZE) == -1) {
+               ::close(fd);
+               throw std::runtime_error("Failed to liten.");
+       }
+
+       this->fd = fd;
+}
+
+Socket::Socket(Socket&& that) : fd(that.fd)
+{
+       that.fd = -1;
+}
+
+Socket& Socket::operator=(Socket&& that)
+{
+       if (this == &that)
+               return *this;
+
+       this->fd = that.fd;
+       that.fd = -1;
+
+       return *this;
+}
+
+Socket::~Socket(void)
+{
+       if (fd != -1)
+               ::close(fd);
+}
+
+Socket Socket::accept(void) const
+{
+       int fd = ::accept(this->fd, nullptr, nullptr);
+       if (fd == -1)
+               throw std::runtime_error("Failed to accept.");
+
+       set_cloexec(fd);
+
+       return Socket(fd);
+}
+
+Socket Socket::connect(const std::string& path)
+{
+       if (path.size() >= sizeof(::sockaddr_un::sun_path))
+               throw std::invalid_argument("Socket path size is wrong.");
+
+       int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd == -1)
+               throw std::runtime_error("Failed to create socket.");
+
+       set_cloexec(fd);
+
+       ::sockaddr_un addr;
+       addr.sun_family = AF_UNIX;
+       ::strncpy(addr.sun_path, path.c_str(), sizeof(::sockaddr_un::sun_path));
+
+       if (addr.sun_path[0] == '@')
+               addr.sun_path[0] = '\0';
+
+       if (::connect(fd, reinterpret_cast<::sockaddr*>(&addr), sizeof(sockaddr_un)) == -1) {
+               ::close(fd);
+               throw std::runtime_error("Failed to connect.");
+       }
+
+       return Socket(fd);
+}
+
+int Socket::getFd(void) const noexcept
+{
+       return this->fd;
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/socket.hpp b/src/vist/transport/socket.hpp
new file mode 100644 (file)
index 0000000..69069f4
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *  Copyright (c) 2018-present 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   socket.hpp
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Define Unix Domain Socket.
+ */
+
+#pragma once
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <cstddef>
+#include <stdexcept>
+#include <string>
+
+namespace vist {
+namespace transport {
+
+class Socket {
+public:
+       explicit Socket(int fd) noexcept;
+       explicit Socket(const std::string& path);
+       virtual ~Socket(void);
+
+       Socket(const Socket&) = delete;
+       Socket& operator=(const Socket&) = delete;
+
+       Socket(Socket&&);
+       Socket& operator=(Socket&&);
+
+       Socket accept(void) const;
+       static Socket connect(const std::string& path);
+
+       template<typename T>
+       void send(const T* buffer, const std::size_t size = sizeof(T)) const;
+
+       template<typename T>
+       void recv(T* buffer, const std::size_t size = sizeof(T)) const;
+
+       int getFd(void) const noexcept;
+
+private:
+       const int MAX_BACKLOG_SIZE = 100;
+
+       int fd = -1;
+};
+
+template<typename T>
+void Socket::send(const T *buffer, const std::size_t size) const
+{
+       std::size_t written = 0;
+       while (written < size) {
+               auto rest = reinterpret_cast<const unsigned char*>(buffer) + written;
+               auto bytes = ::write(this->fd, rest, size - written);
+               if (bytes >= 0)
+                       written += bytes;
+               else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+                       continue;
+               else
+                       std::runtime_error("Failed to write.");
+       }
+}
+
+template<typename T>
+void Socket::recv(T *buffer, const std::size_t size) const
+{
+       std::size_t readen = 0;
+       while (readen < size) {
+               auto rest = reinterpret_cast<unsigned char*>(buffer) + readen;
+               auto bytes = ::read(this->fd, rest, size - readen);
+               if (bytes >= 0)
+                       readen += bytes;
+               else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+                       continue;
+               else
+                       std::runtime_error("Failed to read.");
+       }
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/tests/connection.cpp b/src/vist/transport/tests/connection.cpp
new file mode 100644 (file)
index 0000000..5c34a02
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (c) 2018-present 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 <vist/transport/connection.hpp>
+#include <vist/transport/socket.hpp>
+#include <vist/event/mainloop.hpp>
+#include <vist/event/eventfd.hpp>
+
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+using namespace vist::transport;
+using namespace vist::event;
+
+TEST(TransportTests, socket_communication)
+{
+       std::string sockPath = ("/tmp/vist-test.sock");
+
+       // server-side
+       Mainloop mainloop;
+       Socket socket(sockPath);
+
+       std::string requestSignature = "request signature";
+       int request1 = 100;
+       bool request2 = true;
+       std::string request3 = "request argument";
+
+       std::string responseSignature = "response signature";
+       int response1 = 300;
+       bool response2 = false;
+       std::string response3 = "response argument";
+
+       auto onAccept = [&]() {
+               Connection conn(socket.accept());
+               Message request = conn.recv();
+               EXPECT_EQ(requestSignature, request.signature);
+
+               int recv1;
+               bool recv2;
+               std::string recv3;
+               request.disclose(recv1, recv2, recv3);
+               EXPECT_EQ(request1, recv1);
+               EXPECT_EQ(request2, recv2);
+               EXPECT_EQ(request3, recv3);
+
+               Message reply(Message::Type::Reply, responseSignature);
+               reply.enclose(response1, response2, response3);
+               conn.send(reply);
+
+               mainloop.removeHandler(socket.getFd());
+               mainloop.stop();
+       };
+
+       mainloop.addHandler(socket.getFd(), std::move(onAccept));
+       auto serverThread = std::thread([&]() { mainloop.run(); });
+
+       // client-side
+       Connection conn(sockPath);
+       Message msg(Message::Type::Signal, requestSignature);
+       msg.enclose(request1, request2, request3);
+
+       Message reply = conn.request(msg);
+       EXPECT_EQ(reply.signature, responseSignature);
+
+       int recv1;
+       bool recv2;
+       std::string recv3;
+       reply.disclose(recv1, recv2, recv3);
+       EXPECT_EQ(response1, recv1);
+       EXPECT_EQ(response2, recv2);
+       EXPECT_EQ(response3, recv3);
+
+       if (serverThread.joinable())
+               serverThread.join();
+}
diff --git a/src/vist/transport/tests/socket.cpp b/src/vist/transport/tests/socket.cpp
new file mode 100644 (file)
index 0000000..db3c456
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) 2018-present 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 <vist/transport/socket.hpp>
+
+#include <chrono>
+#include <cstring>
+#include <limits>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using namespace vist::transport;
+
+TEST(TransportTests, socket_read_write)
+{
+       std::string sockPath = "/tmp/vist-test.sock";
+       Socket socket(sockPath);
+
+       int input = std::numeric_limits<int>::max();
+       bool input2 = true;
+
+       int output = 0;
+       bool output2 = false;
+
+       auto client = std::thread([&]() {
+               std::this_thread::sleep_for(std::chrono::seconds(1));
+
+               // Send input to server.
+               Socket connected = Socket::connect(sockPath);
+               connected.send(&input);
+
+               // Recv input2 from server.
+               connected.recv(&output2);
+
+               EXPECT_EQ(input2, output2);
+       });
+
+       Socket accepted = socket.accept();
+
+       // Recv input from client.
+       accepted.recv(&output);
+       EXPECT_EQ(input, output);
+
+       // Send input2 to client.
+       accepted.send(&input2);
+
+       if (client.joinable())
+               client.join();
+}
+
+TEST(TransportTests, socket_abstract)
+{
+       std::string sockPath = "@vist-sock";
+       Socket socket(sockPath);
+
+       int input = std::numeric_limits<int>::max();
+       bool input2 = true;
+
+       int output = 0;
+       bool output2 = false;
+
+       auto client = std::thread([&]() {
+               std::this_thread::sleep_for(std::chrono::seconds(1));
+
+               // Send input to server.
+               Socket connected = Socket::connect(sockPath);
+               connected.send(&input);
+
+               // Recv input2 from server.
+               connected.recv(&output2);
+
+               EXPECT_EQ(input2, output2);
+       });
+
+       Socket accepted = socket.accept();
+
+       // Recv input from client.
+       accepted.recv(&output);
+       EXPECT_EQ(input, output);
+
+       // Send input2 to client.
+       accepted.send(&input2);
+
+       if (client.joinable())
+               client.join();
+}