Add boost::asio based RMI
authorSangwan Kwon <sangwan.kwon@samsung.com>
Thu, 12 Dec 2019 10:11:15 +0000 (19:11 +0900)
committer권상완/Security 2Lab(SR)/Engineer/삼성전자 <sangwan.kwon@samsung.com>
Thu, 12 Dec 2019 10:16:10 +0000 (19:16 +0900)
Signed-off-by: Sangwan Kwon <sangwan.kwon@samsung.com>
23 files changed:
src/osquery/sql/virtual_table.cpp
src/osquery/tables/tizen/policy_admin.cpp
src/vist/CMakeLists.txt
src/vist/archive.hpp
src/vist/client/query.cpp
src/vist/common/archive.cpp
src/vist/exception.hpp
src/vist/rmi/CMakeLists.txt
src/vist/rmi/exposer.cpp [new file with mode: 0644]
src/vist/rmi/exposer.hpp [new file with mode: 0644]
src/vist/rmi/remote.cpp [new file with mode: 0644]
src/vist/rmi/remote.hpp [new file with mode: 0644]
src/vist/rmi/tests/rmi.cpp [new file with mode: 0644]
src/vist/service/vist.cpp
src/vist/transport/CMakeLists.txt
src/vist/transport/client.hpp [new file with mode: 0644]
src/vist/transport/message.cpp
src/vist/transport/message.hpp
src/vist/transport/protocol.cpp [new file with mode: 0644]
src/vist/transport/protocol.hpp [new file with mode: 0644]
src/vist/transport/server.hpp [new file with mode: 0644]
src/vist/transport/tests/protocol.cpp [new file with mode: 0644]
src/vist/transport/tests/server-client.cpp [new file with mode: 0644]

index 6509e98..f2b9352 100644 (file)
@@ -19,6 +19,8 @@
 #include <osquery/system.h>
 #include <osquery/utils/conversions/tryto.h>
 
+#include <osquery/logger.h>
+
 namespace osquery {
 
 FLAG(bool, enable_foreign, false, "Enable no-op foreign virtual tables");
index 4b068dc..66aafe0 100644 (file)
@@ -44,9 +44,6 @@ std::string parseAdmin(const std::string& request, bool insert = true)
        if (document.HasParseError() || !document.IsArray())
                throw std::runtime_error("Cannot parse request.");
 
-       if (document.Size() != 1)
-               throw std::runtime_error("Wrong request format.");
-
        if (insert)
                return std::string(document[0].GetString());
        else
index 07a686b..30d8026 100644 (file)
@@ -57,9 +57,9 @@ ADD_LIBRARY(${TARGET_VIST_COMMON_LIB} STATIC ${${TARGET_VIST_COMMON_LIB}_SRCS})
 
 IF(DEFINED GBS_BUILD)
        TARGET_LINK_LIBRARIES(${TARGET_VIST_COMMON_LIB} ${VIST_COMMON_DEPS_LIBRARIES}
-                                                                                                       pthread glog gflags)
+                                                                                                       pthread glog gflags boost_system)
 ELSE(DEFINED GBS_BUILD)
-       TARGET_LINK_LIBRARIES(${TARGET_VIST_COMMON_LIB} pthread glog gflags)
+       TARGET_LINK_LIBRARIES(${TARGET_VIST_COMMON_LIB} pthread glog gflags boost_system)
 ENDIF(DEFINED GBS_BUILD)
 
 ADD_LIBRARY(${TARGET_VIST_LIB} STATIC ${${TARGET_VIST_LIB}_SRCS})
index f2bd3dc..7cd3ae8 100644 (file)
@@ -88,7 +88,8 @@ public:
 
        unsigned char* get(void) noexcept;
        std::size_t size(void) const noexcept;
-       void reserve(std::size_t size) noexcept;
+       void resize(std::size_t size);
+       std::vector<unsigned char>& getBuffer(void) noexcept;
 
 protected:
        void save(const void* bytes, std::size_t size);
index dac1ab1..6c8ebc6 100644 (file)
@@ -17,7 +17,7 @@
 #include "query.hpp"
 
 #include <vist/logger.hpp>
-#include <vist/rmi/client.hpp>
+#include <vist/rmi/remote.hpp>
 
 namespace {
        const std::string SOCK_ADDR = "/tmp/.vist";
@@ -28,9 +28,9 @@ namespace vist {
 Rows Query::Execute(const std::string& statement)
 {
        INFO(VIST_CLIENT) << "Query execution: " << statement;
-       rmi::Client client(SOCK_ADDR);
+       rmi::Remote remote(SOCK_ADDR);
 
-       return client.invoke<Rows>("Vist::query", statement);
+       return remote.invoke<Rows>("Vist::query", statement);
 }
 
 } // namespace vist
index f7e7e5c..e6ac08d 100644 (file)
@@ -65,14 +65,19 @@ unsigned char* Archive::get(void) noexcept
        return this->buffer.data();
 }
 
+std::vector<unsigned char>& Archive::getBuffer(void) noexcept
+{
+       return this->buffer;
+}
+
 std::size_t Archive::size(void) const noexcept
 {
        return this->buffer.size();
 }
 
-void Archive::reserve(std::size_t size) noexcept
+void Archive::resize(std::size_t size)
 {
-       this->buffer.reserve(size);
+       this->buffer.resize(size);
 }
 
 void Archive::save(const void* bytes, std::size_t size)
index f3f6931..e443136 100644 (file)
@@ -56,7 +56,8 @@ enum class ErrCode {
        LogicError = 0, /// Includes invalid_argument
        RuntimeError,
        BadCast,
-       TypeUnsafed
+       TypeUnsafed,
+       ProtocolBroken
 };
 
 template <typename ErrCode>
index 167ef87..ee53145 100644 (file)
@@ -13,7 +13,9 @@
 #  limitations under the License
 
 ADD_VIST_COMMON_LIBRARY(vist_rmi client.cpp
-                                                                server.cpp)
+                                                                server.cpp
+                                                                exposer.cpp
+                                                                remote.cpp)
 
 FILE(GLOB RMI_TESTS "tests/*.cpp")
 ADD_VIST_TEST(${RMI_TESTS})
diff --git a/src/vist/rmi/exposer.cpp b/src/vist/rmi/exposer.cpp
new file mode 100644 (file)
index 0000000..ce79695
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  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   exposer.cpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @brief  Implementation of Server-side stub for exposing method. 
+ */
+
+#include "exposer.hpp"
+
+#include <vist/transport/message.hpp>
+#include <vist/transport/server.hpp>
+
+#include <string>
+
+namespace vist {
+namespace rmi {
+
+using namespace vist::transport;
+
+class Exposer::Impl {
+public:
+       explicit Impl(Exposer& exposer, const std::string& path);
+
+       void start(void);
+       void stop(void);
+
+private:
+       std::unique_ptr<Server> server;
+};
+
+Exposer::Impl::Impl(Exposer& exposer, const std::string& path)
+{
+       auto dispatcher = [&exposer](Message message) -> Message {
+               std::string function = message.signature;
+               auto iter = exposer.functorMap.find(function);
+               if (iter == exposer.functorMap.end())
+                       THROW(ErrCode::RuntimeError) << "Faild to find function.";
+
+               DEBUG(VIST) << "Remote method invokation: " << function;
+
+               auto functor = iter->second;
+               auto result = functor->invoke(message.buffer);
+
+               Message reply(Message::Type::Reply, function);
+               reply.enclose(result);
+
+               return reply;
+       };
+       
+       this->server = std::make_unique<Server>(path, dispatcher);
+}
+
+void Exposer::Impl::start(void)
+{
+       this->server->run();
+}
+
+void Exposer::Impl::stop(void)
+{
+       this->server->stop();
+}
+
+Exposer::Exposer(const std::string& path) : pImpl(new Impl(*this, path))
+{
+}
+
+void Exposer::start(void)
+{
+       this->pImpl->start();
+}
+
+void Exposer::stop(void)
+{
+       this->pImpl->stop();
+}
+
+void Exposer::ImplDeleter::operator()(Impl* ptr)
+{
+       delete ptr;
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/exposer.hpp b/src/vist/rmi/exposer.hpp
new file mode 100644 (file)
index 0000000..ac9d25e
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ *  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   exposer.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @brief  Server-side stub for exposing method. 
+ */
+
+#pragma once
+
+#include <vist/klass/functor.hpp>
+
+#include <memory>
+#include <string>
+
+namespace vist {
+namespace rmi {
+
+class Exposer final {
+public:
+       explicit Exposer(const std::string& path);
+
+       void start(void);
+       void stop(void);
+
+       template<typename O, typename F>
+       void expose(O&& object, const std::string& name, F&& func);
+
+private:
+       class Impl;
+       struct ImplDeleter
+       {
+               void operator()(Impl*);
+       };
+
+       klass::FunctorMap functorMap;
+       std::unique_ptr<Impl, ImplDeleter> pImpl;
+};
+
+template<typename O, typename F>
+void Exposer::expose(O&& object, const std::string& name, F&& func)
+{
+       auto functor = klass::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/remote.cpp b/src/vist/rmi/remote.cpp
new file mode 100644 (file)
index 0000000..c041b0d
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  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   remote.cpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @brief  Implementation of remote. 
+ */
+
+#include "remote.hpp"
+
+#include <vist/transport/client.hpp>
+
+#include <string>
+#include <mutex>
+
+namespace vist {
+namespace rmi {
+
+using namespace vist::transport;
+
+class Remote::Impl {
+public:
+       explicit Impl(const std::string& path) : client(path)
+       {
+       }
+
+       Message request(Message message)
+       {
+               return this->client.request(message);
+       }
+
+private:
+       Client client;
+};
+
+Remote::Remote(const std::string& path) : pImpl(new Impl(path))
+{
+}
+
+Message Remote::request(Message message)
+{
+       return pImpl->request(message);
+}
+
+void Remote::ImplDeleter::operator()(Impl* ptr)
+{
+       delete ptr;
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/remote.hpp b/src/vist/rmi/remote.hpp
new file mode 100644 (file)
index 0000000..ab95d28
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *  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   remote.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @author Jaemin Ryu (jm77.ryu@samsung.com)
+ * @brief  Client-side stub for invoking remote method.
+ */
+
+#pragma once
+
+#include <vist/transport/message.hpp>
+
+#include <string>
+#include <memory>
+
+namespace vist {
+namespace rmi {
+
+using namespace vist::transport;
+
+class Remote {
+public:
+       explicit Remote(const std::string& path);
+
+       template<typename R, typename... Args>
+       R invoke(const std::string& name, Args&&... args);
+
+private:
+       Message request(Message message);
+
+       class Impl;
+       /// Let compiler know how to destroy Impl
+       struct ImplDeleter
+       {
+               void operator()(Impl*);
+       };
+
+       std::unique_ptr<Impl, ImplDeleter> pImpl;
+};
+
+template<typename R, typename... Args>
+R Remote::invoke(const std::string& method, Args&&... args)
+{
+       Message message(Message::Type::MethodCall, method);
+       message.enclose(std::forward<Args>(args)...);
+
+       Message reply = this->request(message);
+       R result;
+       reply.disclose(result);
+
+       return result;
+}
+
+} // namespace rmi
+} // namespace vist
diff --git a/src/vist/rmi/tests/rmi.cpp b/src/vist/rmi/tests/rmi.cpp
new file mode 100644 (file)
index 0000000..61606b5
--- /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
+ */
+
+#include <vist/rmi/exposer.hpp>
+#include <vist/rmi/remote.hpp>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+using namespace vist::rmi;
+
+// exposer 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, positive)
+{
+       std::string sockPath = ("/tmp/test-exposer");
+
+       // exposer-side
+       Exposer exposer(sockPath);
+
+       auto foo = std::make_shared<Foo>();
+       exposer.expose(foo, "Foo::setName", &Foo::setName);
+       exposer.expose(foo, "Foo::getName", &Foo::getName);
+
+       auto client = std::thread([&]() {
+               // caller-side
+               Remote remote(sockPath);
+
+               std::string param = "RMI-TEST";
+               bool ret = remote.invoke<bool>("Foo::setName", param);
+               EXPECT_EQ(ret, false);
+
+               std::string name = remote.invoke<std::string>("Foo::getName");
+               EXPECT_EQ(name, param);
+
+               exposer.stop();
+       });
+
+       exposer.start();
+
+       if (client.joinable())
+               client.join();
+}
index c63c65c..596c4a3 100644 (file)
@@ -16,7 +16,7 @@
 
 #include "vist.hpp"
 
-#include <vist/rmi/server.hpp>
+#include <vist/rmi/exposer.hpp>
 #include <vist/logger.hpp>
 #include <vist/exception.hpp>
 
@@ -37,11 +37,10 @@ Vist::Vist()
 void Vist::start()
 {
        INFO(VIST) << "Vist daemon starts.";
-       rmi::Server server;
-       server.listen(SOCK_ADDR);
+       rmi::Exposer exposer(SOCK_ADDR);
 
-       server.expose(this, "Vist::query", &Vist::query);
-       server.start();
+       exposer.expose(this, "Vist::query", &Vist::query);
+       exposer.start();
 }
 
 Rows Vist::query(const std::string& statement)
index b3fb7b1..3134599 100644 (file)
@@ -14,7 +14,8 @@
 
 ADD_VIST_COMMON_LIBRARY(vist_transport connection.cpp
                                                                           socket.cpp
-                                                                          message.cpp)
+                                                                          message.cpp
+                                                                          protocol.cpp)
 
 FILE(GLOB TRANSPORT_TESTS "tests/*.cpp")
 ADD_VIST_TEST(${TRANSPORT_TESTS})
diff --git a/src/vist/transport/client.hpp b/src/vist/transport/client.hpp
new file mode 100644 (file)
index 0000000..1f2091b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/*
+ * @file   client.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Protocol compliant client.
+ */
+
+#pragma once
+
+#include <vist/exception.hpp>
+#include <vist/logger.hpp>
+#include <vist/transport/protocol.hpp>
+
+namespace vist {
+namespace transport {
+
+using boost::asio::local::stream_protocol;
+
+class Client {
+public:
+       Client(const std::string& path) : socket(this->context)
+       {
+               try {
+                       this->socket.connect(Protocol::Endpoint(path));
+               }  catch(const std::exception& e) {
+                       ERROR(VIST) << "Failed to connect socket: " << e.what();
+                       std::rethrow_exception(std::current_exception());
+               }
+       }
+
+       inline Message request(Message& message)
+       {
+               return Protocol::Request(this->socket, message);
+       }
+
+private:
+       Protocol::Context context;
+       Protocol::Socket socket;
+};
+
+} // namespace transport
+} // namespace vist
index aca58d6..7f75043 100644 (file)
@@ -34,7 +34,7 @@ Message::Message(unsigned int type, const std::string& signature) :
 
 Message::Message(Header header) : header(header)
 {
-       this->buffer.reserve(this->header.length);
+       this->buffer.resize(this->header.length);
 }
 
 std::size_t Message::size(void) const noexcept
@@ -42,5 +42,15 @@ std::size_t Message::size(void) const noexcept
        return this->header.length;
 }
 
+void Message::resize(std::size_t size)
+{
+       this->buffer.resize(size);
+}
+
+std::vector<unsigned char>& Message::getBuffer(void) noexcept
+{
+       return this->buffer.getBuffer();
+}
+
 } // namespace transport
 } // namespace vist
index f8c832b..4e673fa 100644 (file)
@@ -63,6 +63,8 @@ struct Message final {
        void disclose(Args&... args);
 
        std::size_t size(void) const noexcept;
+       void resize(std::size_t size);
+       std::vector<unsigned char>& getBuffer(void) noexcept;
 
        Header header;
        std::string signature;
diff --git a/src/vist/transport/protocol.cpp b/src/vist/transport/protocol.cpp
new file mode 100644 (file)
index 0000000..c160fb4
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ *  Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "protocol.hpp"
+
+#include <vist/exception.hpp>
+#include <vist/logger.hpp>
+
+namespace vist {
+namespace transport {
+
+Message Protocol::Recv(Socket& socket)
+{
+       Message::Header header;
+       const auto& headerBuffer = boost::asio::buffer(&header, sizeof(Message::Header));
+       auto readen = boost::asio::read(socket, headerBuffer);
+       if (readen != sizeof(Message::Header))
+               THROW(ErrCode::ProtocolBroken) << "Failed to receive message header.";
+
+       Message content(header);
+       const auto& contentBuffer = boost::asio::buffer(content.getBuffer());
+       readen = boost::asio::read(socket, contentBuffer);
+       if (readen != content.size())
+               THROW(ErrCode::ProtocolBroken) << "Failed to receive message content.";
+
+       content.disclose(content.signature);
+
+       return content;
+}
+
+void Protocol::Send(Socket& socket, Message& message)
+{ 
+       const auto& headerBuffer = boost::asio::buffer(&message.header,
+                                                                                                  sizeof(Message::Header));
+       auto written = boost::asio::write(socket, headerBuffer);
+       if (written != sizeof(Message::Header))
+               THROW(ErrCode::ProtocolBroken) << "Failed to send message header.";
+
+       const auto& contentBuffer = boost::asio::buffer(message.getBuffer(), message.size());
+       written = boost::asio::write(socket, contentBuffer);
+       if (written != message.size())
+               THROW(ErrCode::ProtocolBroken) << "Failed to send message content.";
+}
+
+Message Protocol::Request(Socket& socket, Message& message)
+{
+       Protocol::Send(socket, message);
+       return Protocol::Recv(socket);
+}
+
+void Protocol::Async::dispatch(const Task& task)
+{
+       auto self = shared_from_this();
+       const auto& header = boost::asio::buffer(&this->message.header,
+                                                                                        sizeof(Message::Header));
+       auto handler = [self, task](const auto& error, std::size_t size) {
+               if (error) {
+                       if (error == boost::asio::error::eof) {
+                               DEBUG(VIST) << "Socket EoF event occured.";
+                               return;
+                       } else {
+                               THROW(ErrCode::RuntimeError) << error.message();
+                       }
+               }
+
+               if (size != sizeof(Message::Header))
+                       THROW(ErrCode::ProtocolBroken) << error.message();
+
+               /// Resize message buffer to revieved header length.
+               self->message.resize(self->message.size());
+
+               const auto& contentBuffer = boost::asio::buffer(self->message.getBuffer());
+               auto readen = boost::asio::read(self->socket, contentBuffer);
+               if (readen != self->message.size())
+                       THROW(ErrCode::ProtocolBroken) << "Failed to receive message content."
+                               << readen << ", " << self->message.size();
+
+               self->message.disclose(self->message.signature);
+               self->process(task);
+       };
+
+       boost::asio::async_read(self->socket, header, handler);
+}
+
+void Protocol::Async::process(const Task& task)
+{
+       auto result = task(message);
+       /// catch
+       this->message = result;
+       auto self = shared_from_this();
+       const auto& headerBuffer = boost::asio::buffer(&this->message.header,
+                                                                                                  sizeof(Message::Header));
+       auto handler = [self, task](const auto& error, std::size_t size) { 
+               if (error || size != sizeof(Message::Header))
+                       THROW(ErrCode::ProtocolBroken) << "Failed to send message header: "
+                                                                                  << error.message();
+
+               const auto& contentBuffer = boost::asio::buffer(self->message.getBuffer());
+               auto written = boost::asio::write(self->socket, contentBuffer);
+               if (written != self->message.size())
+                       THROW(ErrCode::ProtocolBroken) << "Failed to send message content.";
+
+               // Re-dispatch for next request.
+               self->dispatch(task);
+       };
+
+       boost::asio::async_write(self->socket, headerBuffer, handler);
+}
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/protocol.hpp b/src/vist/transport/protocol.hpp
new file mode 100644 (file)
index 0000000..15b9352
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  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   protocol.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Socket communication protocol between server and client.
+ */
+
+#pragma once
+
+#include <vist/transport/message.hpp>
+
+#include <functional>
+
+#include <boost/asio.hpp>
+
+namespace vist {
+namespace transport {
+
+struct Protocol {
+       using Acceptor = boost::asio::local::stream_protocol::acceptor;
+       using Context = boost::asio::io_service;
+       using Endpoint = boost::asio::local::stream_protocol::endpoint;
+       using Socket = boost::asio::local::stream_protocol::socket;
+       using Task = std::function<Message(Message)>;
+
+       static Message Recv(Socket& socket);
+       static void Send(Socket& socket, Message& message);
+
+       static Message Request(Socket& socket, Message& message);
+
+       /// Passing shared_from_this() to lambda() guarantees
+       /// that the lambda() always refer to a live object.
+       class Async : public std::enable_shared_from_this<Async> {
+       public:
+               explicit Async(Context& context) : socket(context) {}
+               void dispatch(const Task& task);
+               void process(const Task& task);
+
+               inline Socket& getSocket()
+               {
+                       return this->socket;
+               }
+
+       private:
+               Message message; 
+               Socket socket;
+       };
+};
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/server.hpp b/src/vist/transport/server.hpp
new file mode 100644 (file)
index 0000000..eee2b81
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ *  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   server.hpp
+ * @author Sangwan Kwon (sangwan.kwon@samsung.com)
+ * @brief  Protocol compliant server.
+ */
+
+#pragma once
+
+#include <vist/exception.hpp>
+#include <vist/logger.hpp>
+#include <vist/transport/protocol.hpp>
+
+#include <memory>
+
+#include <unistd.h>
+#include <errno.h>
+
+namespace vist {
+namespace transport {
+
+class Server {
+public:
+       Server(const std::string& path, const Protocol::Task& task)
+       {
+               errno = 0;
+               if (::unlink(path.c_str()) == -1 && errno != ENOENT)
+                       THROW(ErrCode::RuntimeError) << "Failed to remove file."; 
+
+               this->acceptor = std::make_unique<Protocol::Acceptor>(this->context,
+                                                                                                                         Protocol::Endpoint(path));
+               this->accept(task);
+       }
+
+       inline void accept(const Protocol::Task& task)
+       {
+               auto asyncSession = std::make_shared<Protocol::Async>(this->context);
+               auto handler = [this, asyncSession, task](const auto& error) {
+                       DEBUG(VIST) << "New session is accepted.";
+
+                       if (error)
+                               THROW(ErrCode::RuntimeError) << error.message();
+
+                       asyncSession->dispatch(task);
+
+                       this->accept(task);
+               };
+               this->acceptor->async_accept(asyncSession->getSocket(), handler);
+       }
+
+       inline void run()
+       {
+               this->context.run();
+       }
+
+       inline void stop()
+       {
+               this->context.stop();
+       }
+
+private:
+       Protocol::Context context;
+       std::unique_ptr<Protocol::Acceptor> acceptor;
+};
+
+} // namespace transport
+} // namespace vist
diff --git a/src/vist/transport/tests/protocol.cpp b/src/vist/transport/tests/protocol.cpp
new file mode 100644 (file)
index 0000000..73c5140
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include <vist/transport/protocol.hpp>
+
+#include <string>
+#include <thread>
+
+#include <gtest/gtest.h>
+#include <boost/asio.hpp>
+
+#include <unistd.h>
+
+using namespace vist::transport;
+using boost::asio::local::stream_protocol;
+
+namespace {
+
+/// Request variables
+std::string requestSignature = "request signature";
+int request1 = 100;
+bool request2 = true;
+std::string request3 = "request argument";
+
+/// Response variables
+std::string responseSignature = "response signature";
+int response1 = 300;
+bool response2 = false;
+std::string response3 = "response argument";
+
+} // anonymous namespace
+
+TEST(ProtocolTests, sync_server_sync_client)
+{
+       std::string sockPath = "vist-test.sock";
+       ::unlink(sockPath.c_str());
+
+       /// Server configuration
+       boost::asio::io_service context;
+       stream_protocol::acceptor acceptor(context, stream_protocol::endpoint(sockPath));
+       stream_protocol::socket sock(context);
+
+       auto handler = [&](const auto& error) {
+               EXPECT_EQ(error, boost::system::errc::success);
+
+               Message request = Protocol::Recv(sock);
+               EXPECT_EQ(request.signature, requestSignature);
+
+               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);
+               Protocol::Send(sock, reply);
+       };
+       acceptor.async_accept(sock, handler);
+
+       auto serverThread = std::thread([&]() {
+               context.run(); 
+       });
+
+       { /// Client configuration
+               boost::asio::io_service context;
+               stream_protocol::socket sock(context);
+               sock.connect(stream_protocol::endpoint(sockPath));
+
+               Message request(Message::Type::MethodCall, requestSignature);
+               request.enclose(request1, request2, request3);
+
+               auto reply = Protocol::Request(sock, request);
+               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);
+       }
+
+       context.stop();
+
+       if (serverThread.joinable())
+               serverThread.join();
+}
+
+TEST(ProtocolTests, async_server_sync_client)
+{
+       std::string sockPath = "vist-test.sock";
+       ::unlink(sockPath.c_str());
+
+       /// Server configuration
+       boost::asio::io_service context;
+       stream_protocol::acceptor acceptor(context, stream_protocol::endpoint(sockPath));
+
+       auto async = std::make_shared<Protocol::Async>(context);
+       auto handler = [&](const auto& error) {
+               EXPECT_EQ(error, boost::system::errc::success);
+               auto task = [&](Message message) -> Message {
+                       EXPECT_EQ(message.signature, requestSignature);
+
+                       int recv1;
+                       bool recv2;
+                       std::string recv3;
+                       message.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);
+                       return reply;
+               };
+
+               async->dispatch(task);
+       };
+       acceptor.async_accept(async->getSocket(), handler);
+
+       auto serverThread = std::thread([&]() {
+               context.run(); 
+       });
+
+       { /// Client configuration
+               boost::asio::io_service context;
+               stream_protocol::socket sock(context);
+               sock.connect(stream_protocol::endpoint(sockPath));
+
+               Message request(Message::Type::MethodCall, requestSignature);
+               request.enclose(request1, request2, request3);
+
+               auto requestClosure = [&]() {
+                       auto reply = Protocol::Request(sock, request);
+                       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);
+               };
+
+               for (int i = 0; i < 3; i++)
+                       requestClosure();
+       }
+
+       context.stop();
+
+       if (serverThread.joinable())
+               serverThread.join();
+}
diff --git a/src/vist/transport/tests/server-client.cpp b/src/vist/transport/tests/server-client.cpp
new file mode 100644 (file)
index 0000000..da316f1
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *  Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include <vist/transport/server.hpp>
+#include <vist/transport/client.hpp>
+
+#include <string>
+#include <thread>
+
+#include <boost/asio.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace vist::transport;
+using boost::asio::local::stream_protocol;
+
+namespace {
+
+/// Request variables
+std::string requestSignature = "request signature";
+int request1 = 100;
+bool request2 = true;
+std::string request3 = "request argument";
+
+/// Response variables
+std::string responseSignature = "response signature";
+int response1 = 300;
+bool response2 = false;
+std::string response3 = "response argument";
+
+} // anonymous namespace
+
+TEST(ServerClientTests, server)
+{
+       std::string sockPath = "vist-test.sock";
+
+       auto task = [&](Message message) -> Message {
+               EXPECT_EQ(message.signature, requestSignature);
+
+               int recv1;
+               bool recv2;
+               std::string recv3;
+               message.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);
+               return reply;
+       };
+
+       Server server(sockPath, task); 
+       auto serverThread = std::thread([&]() {
+               server.run(); 
+       });
+
+       { /// Client configuration
+               auto clientClosure = [&]() {
+                       Client client(sockPath);
+
+                       Message message(Message::Type::MethodCall, requestSignature);
+                       message.enclose(request1, request2, request3);
+
+                       for (int i = 0; i < 3; i++) {
+                               auto reply = client.request(message);
+                               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);
+                       }
+               };
+
+               for (int i = 0; i < 3; i++)
+                       clientClosure();
+       }
+
+       server.stop();
+
+       if (serverThread.joinable())
+               serverThread.join();
+}