From 6959f5e4059f4594e62bac802b781e9167b75bfd Mon Sep 17 00:00:00 2001 From: Sangwan Kwon Date: Thu, 12 Dec 2019 19:11:15 +0900 Subject: [PATCH] Add boost::asio based RMI Signed-off-by: Sangwan Kwon --- src/osquery/sql/virtual_table.cpp | 2 + src/osquery/tables/tizen/policy_admin.cpp | 3 - src/vist/CMakeLists.txt | 4 +- src/vist/archive.hpp | 3 +- src/vist/client/query.cpp | 6 +- src/vist/common/archive.cpp | 9 +- src/vist/exception.hpp | 3 +- src/vist/rmi/CMakeLists.txt | 4 +- src/vist/rmi/exposer.cpp | 98 ++++++++++++ src/vist/rmi/exposer.hpp | 62 ++++++++ src/vist/rmi/remote.cpp | 65 ++++++++ src/vist/rmi/remote.hpp | 69 +++++++++ src/vist/rmi/tests/rmi.cpp | 74 +++++++++ src/vist/service/vist.cpp | 9 +- src/vist/transport/CMakeLists.txt | 3 +- src/vist/transport/client.hpp | 56 +++++++ src/vist/transport/message.cpp | 12 +- src/vist/transport/message.hpp | 2 + src/vist/transport/protocol.cpp | 124 +++++++++++++++ src/vist/transport/protocol.hpp | 65 ++++++++ src/vist/transport/server.hpp | 81 ++++++++++ src/vist/transport/tests/protocol.cpp | 171 +++++++++++++++++++++ src/vist/transport/tests/server-client.cpp | 100 ++++++++++++ 23 files changed, 1005 insertions(+), 20 deletions(-) create mode 100644 src/vist/rmi/exposer.cpp create mode 100644 src/vist/rmi/exposer.hpp create mode 100644 src/vist/rmi/remote.cpp create mode 100644 src/vist/rmi/remote.hpp create mode 100644 src/vist/rmi/tests/rmi.cpp create mode 100644 src/vist/transport/client.hpp create mode 100644 src/vist/transport/protocol.cpp create mode 100644 src/vist/transport/protocol.hpp create mode 100644 src/vist/transport/server.hpp create mode 100644 src/vist/transport/tests/protocol.cpp create mode 100644 src/vist/transport/tests/server-client.cpp diff --git a/src/osquery/sql/virtual_table.cpp b/src/osquery/sql/virtual_table.cpp index 6509e98..f2b9352 100644 --- a/src/osquery/sql/virtual_table.cpp +++ b/src/osquery/sql/virtual_table.cpp @@ -19,6 +19,8 @@ #include #include +#include + namespace osquery { FLAG(bool, enable_foreign, false, "Enable no-op foreign virtual tables"); diff --git a/src/osquery/tables/tizen/policy_admin.cpp b/src/osquery/tables/tizen/policy_admin.cpp index 4b068dc..66aafe0 100644 --- a/src/osquery/tables/tizen/policy_admin.cpp +++ b/src/osquery/tables/tizen/policy_admin.cpp @@ -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 diff --git a/src/vist/CMakeLists.txt b/src/vist/CMakeLists.txt index 07a686b..30d8026 100644 --- a/src/vist/CMakeLists.txt +++ b/src/vist/CMakeLists.txt @@ -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}) diff --git a/src/vist/archive.hpp b/src/vist/archive.hpp index f2bd3dc..7cd3ae8 100644 --- a/src/vist/archive.hpp +++ b/src/vist/archive.hpp @@ -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& getBuffer(void) noexcept; protected: void save(const void* bytes, std::size_t size); diff --git a/src/vist/client/query.cpp b/src/vist/client/query.cpp index dac1ab1..6c8ebc6 100644 --- a/src/vist/client/query.cpp +++ b/src/vist/client/query.cpp @@ -17,7 +17,7 @@ #include "query.hpp" #include -#include +#include 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("Vist::query", statement); + return remote.invoke("Vist::query", statement); } } // namespace vist diff --git a/src/vist/common/archive.cpp b/src/vist/common/archive.cpp index f7e7e5c..e6ac08d 100644 --- a/src/vist/common/archive.cpp +++ b/src/vist/common/archive.cpp @@ -65,14 +65,19 @@ unsigned char* Archive::get(void) noexcept return this->buffer.data(); } +std::vector& 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) diff --git a/src/vist/exception.hpp b/src/vist/exception.hpp index f3f6931..e443136 100644 --- a/src/vist/exception.hpp +++ b/src/vist/exception.hpp @@ -56,7 +56,8 @@ enum class ErrCode { LogicError = 0, /// Includes invalid_argument RuntimeError, BadCast, - TypeUnsafed + TypeUnsafed, + ProtocolBroken }; template diff --git a/src/vist/rmi/CMakeLists.txt b/src/vist/rmi/CMakeLists.txt index 167ef87..ee53145 100644 --- a/src/vist/rmi/CMakeLists.txt +++ b/src/vist/rmi/CMakeLists.txt @@ -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 index 0000000..ce79695 --- /dev/null +++ b/src/vist/rmi/exposer.cpp @@ -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 +#include + +#include + +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; +}; + +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(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 index 0000000..ac9d25e --- /dev/null +++ b/src/vist/rmi/exposer.hpp @@ -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 + +#include +#include + +namespace vist { +namespace rmi { + +class Exposer final { +public: + explicit Exposer(const std::string& path); + + void start(void); + void stop(void); + + template + void expose(O&& object, const std::string& name, F&& func); + +private: + class Impl; + struct ImplDeleter + { + void operator()(Impl*); + }; + + klass::FunctorMap functorMap; + std::unique_ptr pImpl; +}; + +template +void Exposer::expose(O&& object, const std::string& name, F&& func) +{ + auto functor = klass::make_functor_ptr(std::forward(object), std::forward(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 index 0000000..c041b0d --- /dev/null +++ b/src/vist/rmi/remote.cpp @@ -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 + +#include +#include + +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 index 0000000..ab95d28 --- /dev/null +++ b/src/vist/rmi/remote.hpp @@ -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 + +#include +#include + +namespace vist { +namespace rmi { + +using namespace vist::transport; + +class Remote { +public: + explicit Remote(const std::string& path); + + template + 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 pImpl; +}; + +template +R Remote::invoke(const std::string& method, Args&&... args) +{ + Message message(Message::Type::MethodCall, method); + message.enclose(std::forward(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 index 0000000..61606b5 --- /dev/null +++ b/src/vist/rmi/tests/rmi.cpp @@ -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 +#include + +#include +#include +#include +#include + +#include + +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(); + 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("Foo::setName", param); + EXPECT_EQ(ret, false); + + std::string name = remote.invoke("Foo::getName"); + EXPECT_EQ(name, param); + + exposer.stop(); + }); + + exposer.start(); + + if (client.joinable()) + client.join(); +} diff --git a/src/vist/service/vist.cpp b/src/vist/service/vist.cpp index c63c65c..596c4a3 100644 --- a/src/vist/service/vist.cpp +++ b/src/vist/service/vist.cpp @@ -16,7 +16,7 @@ #include "vist.hpp" -#include +#include #include #include @@ -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) diff --git a/src/vist/transport/CMakeLists.txt b/src/vist/transport/CMakeLists.txt index b3fb7b1..3134599 100644 --- a/src/vist/transport/CMakeLists.txt +++ b/src/vist/transport/CMakeLists.txt @@ -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 index 0000000..1f2091b --- /dev/null +++ b/src/vist/transport/client.hpp @@ -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 +#include +#include + +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 diff --git a/src/vist/transport/message.cpp b/src/vist/transport/message.cpp index aca58d6..7f75043 100644 --- a/src/vist/transport/message.cpp +++ b/src/vist/transport/message.cpp @@ -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& Message::getBuffer(void) noexcept +{ + return this->buffer.getBuffer(); +} + } // namespace transport } // namespace vist diff --git a/src/vist/transport/message.hpp b/src/vist/transport/message.hpp index f8c832b..4e673fa 100644 --- a/src/vist/transport/message.hpp +++ b/src/vist/transport/message.hpp @@ -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& 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 index 0000000..c160fb4 --- /dev/null +++ b/src/vist/transport/protocol.cpp @@ -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 +#include + +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 index 0000000..15b9352 --- /dev/null +++ b/src/vist/transport/protocol.hpp @@ -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 + +#include + +#include + +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; + + 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 { + 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 index 0000000..eee2b81 --- /dev/null +++ b/src/vist/transport/server.hpp @@ -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 +#include +#include + +#include + +#include +#include + +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(this->context, + Protocol::Endpoint(path)); + this->accept(task); + } + + inline void accept(const Protocol::Task& task) + { + auto asyncSession = std::make_shared(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 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 index 0000000..73c5140 --- /dev/null +++ b/src/vist/transport/tests/protocol.cpp @@ -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 + +#include +#include + +#include +#include + +#include + +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(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 index 0000000..da316f1 --- /dev/null +++ b/src/vist/transport/tests/server-client.cpp @@ -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 +#include + +#include +#include + +#include + +#include + +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(); +} -- 2.34.1