diff --git a/net/include/ftl/net/dispatcher.hpp b/net/include/ftl/net/dispatcher.hpp index a2a8f332f957581420e277c1c7fe92b12864f35a..c358ad10495e5b0790658342fda782b939e6bde5 100644 --- a/net/include/ftl/net/dispatcher.hpp +++ b/net/include/ftl/net/dispatcher.hpp @@ -6,7 +6,7 @@ #include <memory> #include <tuple> #include <functional> -#include <iostream> +//#include <iostream> namespace ftl { @@ -38,9 +38,9 @@ class Socket; class Dispatcher { public: - Dispatcher(Socket *s) : sock_(s) {} + Dispatcher() {} - void dispatch(const std::string &msg); + void dispatch(Socket &, const std::string &msg); template <typename F> void bind(std::string const &name, F func, @@ -123,7 +123,6 @@ class Dispatcher { std::tuple<uint32_t, uint32_t, msgpack::object, msgpack::object>; private: - ftl::net::Socket *sock_; std::unordered_map<std::string, adaptor_type> funcs_; static void enforce_arg_count(std::string const &func, std::size_t found, @@ -131,9 +130,9 @@ class Dispatcher { void enforce_unique_name(std::string const &func); - void dispatch(const msgpack::object &msg); - void dispatch_call(const msgpack::object &msg); - void dispatch_notification(msgpack::object const &msg); + void dispatch(Socket &, const msgpack::object &msg); + void dispatch_call(Socket &, const msgpack::object &msg); + void dispatch_notification(Socket &, msgpack::object const &msg); }; } diff --git a/net/include/ftl/net/func_traits.hpp b/net/include/ftl/net/func_traits.hpp index 6ea7cf2f317079fe55cf5a2eef5c4391b88ffcbb..ae12b335a76d4231c7e497dc0c5440dc7301f5a6 100644 --- a/net/include/ftl/net/func_traits.hpp +++ b/net/include/ftl/net/func_traits.hpp @@ -14,9 +14,6 @@ namespace internal { template<typename T> using invoke = typename T::type; -template <typename T, typename... ARGS> -struct first_type { typedef T type; }; - template<typename T, T I> struct constant : std::integral_constant<T, I> {}; diff --git a/net/include/ftl/net/listener.hpp b/net/include/ftl/net/listener.hpp index e6f74a8bc4c7065e49a22e7dfd4283ba5a1575c3..e24d4fda04339b7e18a71caeb3bfc3fbc42dfa8f 100644 --- a/net/include/ftl/net/listener.hpp +++ b/net/include/ftl/net/listener.hpp @@ -17,6 +17,8 @@ namespace ftl { namespace net { +class Protocol; + class Listener { public: Listener(const char *uri); @@ -27,11 +29,14 @@ class Listener { void close(); int _socket() { return descriptor_; } + void setProtocol(Protocol *p) { default_proto_ = p; } + void connection(std::shared_ptr<Socket> &s); void onConnection(connecthandler_t h) { handler_connect_.push_back(h); }; private: int descriptor_; + Protocol *default_proto_; sockaddr_in slocalAddr; std::vector<connecthandler_t> handler_connect_; }; diff --git a/net/include/ftl/net/protocol.hpp b/net/include/ftl/net/protocol.hpp index 1f2429bf436079d55e09e0b5594006102e660d22..18d6a5918aeea1a2b24bee22235697ffe635a6a3 100644 --- a/net/include/ftl/net/protocol.hpp +++ b/net/include/ftl/net/protocol.hpp @@ -1,30 +1,24 @@ #ifndef _FTL_NET_PROTOCOL_HPP_ #define _FTL_NET_PROTOCOL_HPP_ +#include <ftl/net/func_traits.hpp> +#include <ftl/net/dispatcher.hpp> +#include <map> +#include <string> + #define FTL_PROTOCOL_HS1 0x0001 // Handshake step 1 #define FTL_PROTOCOL_HS2 0x0002 // Handshake step 2 #define FTL_PROTOCOL_RPC 0x0100 #define FTL_PROTOCOL_RPCRETURN 0x0101 -#define FTL_PROTOCOL_P2P 0x1000 +#define FTL_PROTOCOL_FREE 0x1000 // Custom protocols above this namespace ftl { namespace net { -static const uint32_t MAGIC = 0x23995621; - -static const uint8_t PATCH = 0; -static const uint8_t MINOR = 0; -static const uint8_t MAJOR = 1; - -inline uint32_t version(int maj, int min, int pat) { - return (maj << 16) | (min << 8) | pat; -} - -inline uint32_t version() { - return version(MAJOR, MINOR, PATCH); -} +class Reader; +class Socket; #pragma pack(push,1) @@ -34,14 +28,69 @@ struct Header { }; struct Handshake { - uint32_t magic; - uint32_t version; - char peerid[16]; + uint64_t proto; // The protocol the other party is expected to use. + char peerid[16]; // GUID for the origin peer. + char reserved_[32]; // RESERVED, must be 0. }; #pragma pack(pop) +/** + * Each instance of this Protocol class represents a specific protocol. A + * protocol is a set of RPC bindings and raw message handlers. A protocol is + * identified, selected and validated using an automatically generated hash of + * all its supported bindings. The choice of protocol for a socket is made + * during the initial connection handshake. + */ +class Protocol { + public: + friend class Socket; + + public: + Protocol(uint64_t id); + ~Protocol(); + + /** + * Bind a function to an RPC call name. + */ + template <typename F> + void bind(const std::string &name, F func); + + /** + * Bind a function to a raw message type. + */ + void bind(int service, std::function<void(uint32_t,Socket&)> func); + + // broadcast? + + uint64_t id() const { return id_; } + + static Protocol *find(uint64_t id); + + protected: + void dispatchRPC(Socket &, const std::string &d); + void dispatchReturn(Socket &, const std::string &d); + void dispatchRaw(uint32_t service, Socket &); + + void addSocket(std::shared_ptr<Socket> s); + void removeSocket(const Socket &s); + + private: + ftl::net::Dispatcher disp_; + std::map<uint32_t,std::function<void(uint32_t,Socket&)>> handlers_; + uint64_t id_; + + static std::map<uint64_t,Protocol*> protocols__; +}; + +// --- Template Implementations ------------------------------------------------ +template <typename F> +void Protocol::bind(const std::string &name, F func) { + disp_.bind(name, func, + typename ftl::internal::func_kind_info<F>::result_kind(), + typename ftl::internal::func_kind_info<F>::args_kind()); +} }; }; diff --git a/net/include/ftl/net/socket.hpp b/net/include/ftl/net/socket.hpp index ce515bce796fa0ff4e92ca8e47598f9757cfa4d0..056fcc6ea445bdfa5f77a917b69c5c3469f58eb1 100644 --- a/net/include/ftl/net/socket.hpp +++ b/net/include/ftl/net/socket.hpp @@ -3,8 +3,6 @@ #include <glog/logging.h> #include <ftl/net.hpp> -#include <ftl/net/handlers.hpp> -#include <ftl/net/dispatcher.hpp> #include <ftl/net/protocol.hpp> #ifndef WIN32 @@ -20,6 +18,8 @@ #include <sstream> #include <type_traits> +extern bool _run(bool blocking, bool nodelay); + namespace ftl { namespace net { @@ -39,12 +39,17 @@ struct caller : virtual_caller { * function and not to be created directly. */ class Socket { + public: + friend bool ::_run(bool blocking, bool nodelay); public: Socket(const char *uri); Socket(int s); ~Socket(); int close(); + + void setProtocol(Protocol *p); + Protocol *protocol() const { return proto_; } /** * Get the internal OS dependent socket. @@ -59,18 +64,6 @@ class Socket { * the same as the initial connection string on the client. */ std::string getURI() const { return uri_; }; - - /** - * Bind a function to an RPC call name. - */ - template <typename F> - void bind(const std::string &name, F func); - - /** - * Bind a function to a raw message type. - */ - void bind(uint32_t service, std::function<void(Socket&, - const std::string&)> func); /** * Non-blocking Remote Procedure Call using a callback function. @@ -99,42 +92,60 @@ class Socket { int send2(uint32_t service, const std::string &data1, const std::string &data2); + template <typename T> + int read(T *b, size_t count=1) { + static_assert(std::is_trivial<T>::value); + return read((char*)b, sizeof(T)*count); + } + + //template <> + int read(char *b, size_t count); + int read(std::string &s, size_t count=0); + + template <typename T> + int read(T &b) { + return read(&b); + } + + size_t size() const { return header_->size-4; } + /** * Internal handlers for specific event types. This should be private but * is current here for testing purposes. * @{ */ - void dispatchRPC(const std::string &d) { disp_.dispatch(d); } - void dispatchReturn(const std::string &d); void handshake1(const std::string &d); void handshake2(const std::string &d); /** @} */ - void onError(sockerrorhandler_t handler) {} + //void onError(sockerrorhandler_t handler) {} void onConnect(std::function<void(Socket&)> f); - void onDisconnect(sockdisconnecthandler_t handler) {} + //void onDisconnect(sockdisconnecthandler_t handler) {} - bool data(); - void error(); + protected: + bool data(); // Process one message from socket + void error(); // Process one error from socket private: // Functions void _connected(); void _updateURI(); + void _dispatchReturn(const std::string &d); private: // Data bool valid_; bool connected_; - uint32_t version_; int sock_; size_t pos_; + size_t gpos_; char *buffer_; + ftl::net::Header *header_; + char *data_; std::string uri_; std::string peerid_; + + Protocol *proto_; - ftl::net::Dispatcher disp_; - - std::map<uint32_t,std::function<void(Socket&,const std::string&)>> handlers_; std::vector<std::function<void(Socket&)>> connect_handlers_; std::map<int, std::unique_ptr<virtual_caller>> callbacks_; @@ -146,13 +157,6 @@ class Socket { // --- Inline Template Implementations ----------------------------------------- -template <typename F> -void Socket::bind(const std::string &name, F func) { - disp_.bind(name, func, - typename ftl::internal::func_kind_info<F>::result_kind(), - typename ftl::internal::func_kind_info<F>::args_kind()); -} - //template <typename T, typename... ARGS> template <typename R, typename... ARGS> R Socket::call(const std::string &name, ARGS... args) { diff --git a/net/src/dispatcher.cpp b/net/src/dispatcher.cpp index 2514b403118f75cd7609b98cfa43c60481da4904..bc7a842aca41798f4e4f1b7d07c10e82b148ec3e 100644 --- a/net/src/dispatcher.cpp +++ b/net/src/dispatcher.cpp @@ -3,6 +3,8 @@ #include <ftl/net/socket.hpp> #include <iostream> +using ftl::net::Socket; + /*static std::string hexStr(const std::string &s) { const char *data = s.data(); @@ -14,25 +16,25 @@ return ss.str(); }*/ -void ftl::net::Dispatcher::dispatch(const std::string &msg) { +void ftl::net::Dispatcher::dispatch(Socket &s, const std::string &msg) { //std::cout << "Received dispatch : " << hexStr(msg) << std::endl; auto unpacked = msgpack::unpack(msg.data(), msg.size()); - dispatch(unpacked.get()); + dispatch(s, unpacked.get()); } -void ftl::net::Dispatcher::dispatch(const msgpack::object &msg) { +void ftl::net::Dispatcher::dispatch(Socket &s, const msgpack::object &msg) { switch (msg.via.array.size) { case 3: - dispatch_notification(msg); break; + dispatch_notification(s, msg); break; case 4: - dispatch_call(msg); break; + dispatch_call(s, msg); break; default: LOG(ERROR) << "Unrecognised msgpack : " << msg.via.array.size; return; } } -void ftl::net::Dispatcher::dispatch_call(const msgpack::object &msg) { +void ftl::net::Dispatcher::dispatch_call(Socket &s, const msgpack::object &msg) { call_t the_call; msg.convert(the_call); @@ -44,7 +46,7 @@ void ftl::net::Dispatcher::dispatch_call(const msgpack::object &msg) { auto &&name = std::get<2>(the_call); auto &&args = std::get<3>(the_call); - LOG(INFO) << "RPC " << name << "() <- " << sock_->getURI(); + LOG(INFO) << "RPC " << name << "() <- " << s.getURI(); auto it_func = funcs_.find(name); @@ -57,14 +59,14 @@ void ftl::net::Dispatcher::dispatch_call(const msgpack::object &msg) { //std::cout << " RESULT " << result.as<std::string>() << std::endl; - sock_->send(FTL_PROTOCOL_RPCRETURN, buf.str()); + s.send(FTL_PROTOCOL_RPCRETURN, buf.str()); } catch (...) { throw; } } } -void ftl::net::Dispatcher::dispatch_notification(msgpack::object const &msg) { +void ftl::net::Dispatcher::dispatch_notification(Socket &s, msgpack::object const &msg) { notification_t the_call; msg.convert(the_call); diff --git a/net/src/listener.cpp b/net/src/listener.cpp index 016af218574eea56fa6e11e05d4d29f1d7cc643c..497f1459a3ba40b9b17f9c33c9fa97fa58478fc1 100644 --- a/net/src/listener.cpp +++ b/net/src/listener.cpp @@ -111,11 +111,9 @@ Listener::~Listener() { } void Listener::connection(shared_ptr<Socket> &s) { - ftl::net::Handshake hs1; - hs1.magic = ftl::net::MAGIC; - hs1.version = ftl::net::version(); - s->send(FTL_PROTOCOL_HS1, std::string((char*)&hs1, sizeof(hs1))); - LOG(INFO) << "Handshake initiated with " << s->getURI(); + if (default_proto_) { + s->setProtocol(default_proto_); + } for (auto h : handler_connect_) h(s); } diff --git a/net/src/protocol.cpp b/net/src/protocol.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1e511df907e4765431d43d8fdd8615e050ee2a67 --- /dev/null +++ b/net/src/protocol.cpp @@ -0,0 +1,45 @@ +#include <glog/logging.h> +#include <ftl/net/socket.hpp> +#include <ftl/net/protocol.hpp> +#include <functional> + +using ftl::net::Socket; +using ftl::net::Protocol; + +std::map<uint64_t,Protocol*> Protocol::protocols__; + +Protocol *Protocol::find(uint64_t id) { + if (protocols__.count(id) > 0) return protocols__[id]; + else return NULL; +} + +Protocol::Protocol(uint64_t id) : id_(id) { + protocols__[id] = this; +} + +Protocol::~Protocol() { + protocols__.erase(id_); + // TODO Make sure all dependent sockets are closed! +} + +void Protocol::bind(int service, std::function<void(uint32_t,Socket&)> func) { + if (handlers_.count(service) == 0) { + handlers_[service] = func; + } else { + LOG(ERROR) << "Message service " << service << " already bound"; + } +} + +void Protocol::dispatchRPC(Socket &s, const std::string &d) { + disp_.dispatch(s,d); +} + +void Protocol::dispatchRaw(uint32_t service, Socket &s) { + // Lookup raw message handler + if (handlers_.count(service) > 0) { + handlers_[service](service, s); + } else { + LOG(ERROR) << "Unrecognised service request (" << service << ") from " << s.getURI(); + } +} + diff --git a/net/src/socket.cpp b/net/src/socket.cpp index c1f6e2816cf8de327232250ea437668064631f7c..506847b3690959e9d8692f41114e0e1491daf820 100644 --- a/net/src/socket.cpp +++ b/net/src/socket.cpp @@ -120,17 +120,21 @@ static int wsConnect(URI &uri) { return 1; } -Socket::Socket(int s) : sock_(s), pos_(0), disp_(this) { +Socket::Socket(int s) : sock_(s), pos_(0), proto_(nullptr) { valid_ = true; buffer_ = new char[BUFFER_SIZE]; + header_ = (Header*)buffer_; + data_ = buffer_+sizeof(Header); connected_ = false; _updateURI(); } -Socket::Socket(const char *pUri) : pos_(0), uri_(pUri), disp_(this) { +Socket::Socket(const char *pUri) : pos_(0), uri_(pUri), proto_(nullptr) { // Allocate buffer buffer_ = new char[BUFFER_SIZE]; + header_ = (Header*)buffer_; + data_ = buffer_+sizeof(Header); URI uri(pUri); @@ -191,6 +195,17 @@ int Socket::close() { return 0; } +void Socket::setProtocol(Protocol *p) { + if (proto_ == p) return; + if (proto_ && proto_->id() == p->id()) return; + + proto_ = p; + ftl::net::Handshake hs1; + hs1.proto = p->id(); + send(FTL_PROTOCOL_HS1, std::string((char*)&hs1, sizeof(hs1))); + LOG(INFO) << "Handshake initiated with " << uri_; +} + void Socket::error() { int err; uint32_t optlen = sizeof(err); @@ -249,27 +264,40 @@ bool Socket::data() { auto d = std::string(buffer_+8, len-4); pos_ = 0; // DODGY, processing messages inside handlers is dangerous. + gpos_ = 0; if (service == FTL_PROTOCOL_HS1 && !connected_) { handshake1(d); } else if (service == FTL_PROTOCOL_HS2 && !connected_) { handshake2(d); } else if (service == FTL_PROTOCOL_RPC) { - dispatchRPC(d); + if (proto_) proto_->dispatchRPC(*this, d); + else LOG(WARNING) << "No protocol set for socket " << uri_; } else if (service == FTL_PROTOCOL_RPCRETURN) { - dispatchReturn(d); + _dispatchReturn(d); } else { - // Lookup raw message handler - if (handlers_.count(service) > 0) { - handlers_[service](*this, d); - } else { - LOG(ERROR) << "Unrecognised service request (" << service << ") from " << uri_; - } + if (proto_) proto_->dispatchRaw(service, *this); + else LOG(WARNING) << "No protocol set for socket " << uri_; } return true; } +int Socket::read(char *b, size_t count) { + if (count > size()) LOG(WARNING) << "Reading too much data for service " << header_->service; + count = (count > size() || count==0) ? size() : count; + // TODO, utilise recv directly here... + memcpy(b,data_+gpos_,count); + gpos_+=count; + return count; +} + +int Socket::read(std::string &s, size_t count) { + count = (count > size() || count==0) ? size() : count; + s = std::string(data_+gpos_,count); + return count; +} + void Socket::handshake1(const std::string &d) { ftl::net::Handshake *hs; if (d.size() != sizeof(ftl::net::Handshake)) { @@ -279,23 +307,22 @@ void Socket::handshake1(const std::string &d) { } hs = (ftl::net::Handshake*)d.data(); - if (hs->magic != ftl::net::MAGIC) { - LOG(ERROR) << "Handshake magic failed for " << uri_; + auto proto = Protocol::find(hs->proto); + if (proto == NULL) { + LOG(ERROR) << "Protocol (" << hs->proto << ") not found during handshake for " << uri_; close(); return; + } else { + proto_ = proto; } - - version_ = (hs->version > ftl::net::version()) ? - ftl::net::version() : - hs->version; peerid_ = std::string(&hs->peerid[0],16); ftl::net::Handshake hs2; - hs2.magic = ftl::net::MAGIC; - hs2.version = version_; + //hs2.magic = ftl::net::MAGIC; + //hs2.version = version_; // TODO Set peerid; send(FTL_PROTOCOL_HS2, std::string((char*)&hs2, sizeof(hs2))); - LOG(INFO) << "Handshake v" << version_ << " confirmed from " << uri_; + LOG(INFO) << "Handshake" << " confirmed from " << uri_; _connected(); } @@ -308,7 +335,7 @@ void Socket::handshake2(const std::string &d) { } hs = (ftl::net::Handshake*)d.data(); - if (hs->magic != ftl::net::MAGIC) { + /*if (hs->magic != ftl::net::MAGIC) { LOG(ERROR) << "Handshake magic failed for " << uri_; close(); return; @@ -316,13 +343,13 @@ void Socket::handshake2(const std::string &d) { version_ = (hs->version > ftl::net::version()) ? ftl::net::version() : - hs->version; + hs->version;*/ peerid_ = std::string(&hs->peerid[0],16); LOG(INFO) << "Handshake finalised for " << uri_; _connected(); } -void Socket::dispatchReturn(const std::string &d) { +void Socket::_dispatchReturn(const std::string &d) { auto unpacked = msgpack::unpack(d.data(), d.size()); Dispatcher::response_t the_result; unpacked.get().convert(the_result); @@ -363,15 +390,6 @@ void Socket::_connected() { //connect_handlers_.clear(); } -void Socket::bind(uint32_t service, std::function<void(Socket&, - const std::string&)> func) { - if (handlers_.count(service) == 0) { - handlers_[service] = func; - } else { - LOG(ERROR) << "Message service " << service << " already bound"; - } -} - int Socket::send(uint32_t service, const std::string &data) { ftl::net::Header h; h.size = data.size()+4; diff --git a/net/test/CMakeLists.txt b/net/test/CMakeLists.txt index 2e7aff73cc0c86b3e6d3268fd82732d5897684c2..f9a3d118508499360e2a14a05cff60f1ac4c091d 100644 --- a/net/test/CMakeLists.txt +++ b/net/test/CMakeLists.txt @@ -10,6 +10,32 @@ add_executable(rpc_test EXCLUDE_FROM_ALL target_include_directories(rpc_test PUBLIC ${PROJECT_SOURCE_DIR}/include) target_link_libraries(rpc_test uriparser glog) +add_executable(protocol_unit EXCLUDE_FROM_ALL + ./tests.cpp + ./protocol_unit.cpp +) +target_include_directories(protocol_unit PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(protocol_unit glog) + +add_executable(socket_unit EXCLUDE_FROM_ALL + ./tests.cpp + ./socket_unit.cpp +) +target_include_directories(socket_unit PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(socket_unit uriparser glog) + +add_executable(net_integration EXCLUDE_FROM_ALL + ./tests.cpp + ./net_integration.cpp + ../src/socket.cpp + ../src/dispatcher.cpp + ../src/listener.cpp + ../src/protocol.cpp + ../src/net.cpp +) +target_include_directories(net_integration PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(net_integration uriparser glog) + add_executable(socket_test EXCLUDE_FROM_ALL ./tests.cpp ./net_raw.cpp @@ -25,5 +51,5 @@ target_include_directories(socket_test PUBLIC ${PROJECT_SOURCE_DIR}/include) target_link_libraries(socket_test uriparser glog) add_custom_target(tests) -add_dependencies(tests rpc_test socket_test) +add_dependencies(tests rpc_test socket_test socket_unit protocol_unit net_integration) diff --git a/net/test/net_integration.cpp b/net/test/net_integration.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d44c815dd40afdf80d378674fe19cf12daa0218 --- /dev/null +++ b/net/test/net_integration.cpp @@ -0,0 +1,51 @@ +#include "catch.hpp" +#include <ftl/net.hpp> +#include <ftl/net/socket.hpp> +#include <ftl/net/listener.hpp> + +#include <memory> + +using ftl::net::Socket; +using ftl::net::Protocol; +using std::shared_ptr; + +TEST_CASE("Net Integration", "[integrate]") { + std::string data; + + Protocol p(143); + + p.bind("add", [](int a, int b) { + return a + b; + }); + + p.bind(100, [&data](uint32_t m, Socket &s) { + s.read(data); + }); + + auto l = ftl::net::listen("tcp://localhost:9000"); + REQUIRE( l->isListening() ); + l->setProtocol(&p); + + shared_ptr<Socket> s1; + l->onConnection([&s1](auto &s) { s1 = s; }); + + shared_ptr<Socket> s2 = ftl::net::connect("tcp://localhost:9000"); + + ftl::net::wait(); // TODO, make setProtocol block until handshake complete + ftl::net::wait(); + REQUIRE( s1 != nullptr ); + REQUIRE( s2 != nullptr ); + + REQUIRE( s1->isConnected() ); + REQUIRE( s2->isConnected() ); + + REQUIRE( s1->call<int>("add", 5, 6) == 11 ); + REQUIRE( s2->call<int>("add", 10, 5) == 15); + + s1->send(100, "hello world"); + ftl::net::wait(); + // TODO s2->wait(100); + + REQUIRE( data == "hello world" ); +} + diff --git a/net/test/net_raw.cpp b/net/test/net_raw.cpp index 7c559d97c0d1b1844c6410b41a0086b7618fd60f..3f2fdfd7e1560c26044436447e3ed226757ef182 100644 --- a/net/test/net_raw.cpp +++ b/net/test/net_raw.cpp @@ -295,75 +295,3 @@ TEST_CASE("net::listen()", "[net]") { } } -TEST_CASE("Socket.bind(int)", "[net]") { - // Need a fake server... - init_server(); - shared_ptr<Socket> sock = ftl::net::connect("tcp://127.0.0.1:7077"); - REQUIRE(sock->isValid()); - accept_connection(); - ftl::net::wait(); // Wait for handshake - REQUIRE(sock->isConnected()); - - SECTION("small valid message") { - send_json(1, "{message: \"Hello\"}"); - - bool msg = false; - - sock->bind(1, [&](Socket &s, const std::string &data) { - REQUIRE(data == "{message: \"Hello\"}"); - msg = true; - }); - - ftl::net::wait(); - REQUIRE(msg); - } - - SECTION("empty message") { - send_json(1, ""); - - bool msg = false; - - sock->bind(1, [&](Socket &s, const std::string &data) { - REQUIRE(data == ""); - msg = true; - }); - - ftl::net::wait(); - REQUIRE(msg); - } - - SECTION("multiple valid messages") { - send_json(1, "{message: \"Hello\"}"); - send_json(1, "{test: \"world\"}"); - - int msg = 0; - - sock->bind(1, [&](Socket &s, const std::string &data) { - if (msg == 0) REQUIRE(data == "{message: \"Hello\"}"); - else REQUIRE(data == "{test: \"world\"}"); - msg++; - }); - - ftl::net::wait(); // MSG 1 - ftl::net::wait(); // MSG 2 - REQUIRE(msg == 2); - } - - SECTION("disconnected does not get message") { - send_json(1, "world"); - - bool msg = false; - - sock->bind(1, [&](Socket &s, const std::string &data) { - msg = true; - }); - - sock->close(); - - ftl::net::wait(); - REQUIRE(!msg); - } - - fin_server(); -} - diff --git a/net/test/protocol_unit.cpp b/net/test/protocol_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..33ae20ffc0b4878869ffcdee9bbb1eae7f1f9ba8 --- /dev/null +++ b/net/test/protocol_unit.cpp @@ -0,0 +1,162 @@ +#include "catch.hpp" +#include <ftl/net/protocol.hpp> + +using ftl::net::Protocol; + +// --- Mock -------------------------------------------------------------------- + +#define _FTL_NET_SOCKET_HPP_ // Prevent include + +namespace ftl { +namespace net { +class Socket { + public: + std::string getURI() { return "mock://"; } + int send(int msg, const std::string &d) { return 0; } +}; +}; +}; + +using ftl::net::Socket; + +class MockProtocol : public Protocol { + public: + MockProtocol() : Protocol(33) {} + void mock_dispatchRPC(Socket &s, const std::string &d) { dispatchRPC(s,d); } + void mock_dispatchReturn(Socket &s, const std::string &d) { dispatchReturn(s,d); } + void mock_dispatchRaw(uint32_t msg, Socket &s) { dispatchRaw(msg,s); } +}; + +// --- Support ----------------------------------------------------------------- + +// --- Files to test ----------------------------------------------------------- + +#include "../src/protocol.cpp" +#include "../src/dispatcher.cpp" + +// --- Tests ------------------------------------------------------------------- + +TEST_CASE("Protocol::bind(int,...)", "[proto]") { + MockProtocol p; + Socket s; + + SECTION("a valid bind and dispatch") { + bool msg = false; + + p.bind(5, [&](uint32_t m, Socket &s) { + msg = true; + }); + + p.mock_dispatchRaw(5, s); + REQUIRE(msg); + } + + SECTION("an invalid dispatch") { + bool msg = false; + + p.bind(5, [&](uint32_t m, Socket &s) { + msg = true; + }); + + p.mock_dispatchRaw(6, s); + REQUIRE( !msg ); + // TODO How is failure reported? + } +} + +TEST_CASE("Protocol::bind(string,...)", "[proto]") { + MockProtocol p; + Socket s; + + SECTION("no argument bind with valid dispatch") { + bool called = false; + + p.bind("test1", [&]() { + called = true; + }); + + auto args_obj = std::make_tuple(); + auto call_obj = std::make_tuple(0,0,"test1",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + p.mock_dispatchRPC(s, buf.str()); + REQUIRE( called ); + } + + SECTION("multiple bindings") { + bool called1 = false; + bool called2 = false; + + p.bind("test1", [&]() { + called1 = true; + }); + p.bind("test2", [&]() { + called2 = true; + }); + + auto args_obj = std::make_tuple(); + auto call_obj = std::make_tuple(0,0,"test2",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + p.mock_dispatchRPC(s, buf.str()); + REQUIRE( !called1 ); + REQUIRE( called2 ); + } + + SECTION("one argument bind with valid dispatch") { + bool called = false; + + p.bind("test1", [&](int a) { + called = true; + REQUIRE( a == 5 ); + }); + + auto args_obj = std::make_tuple(5); + auto call_obj = std::make_tuple(0,0,"test1",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + p.mock_dispatchRPC(s, buf.str()); + REQUIRE( called ); + } + + SECTION("two argument bind fake dispatch") { + bool called = false; + + p.bind("test1", [&](int a, float b) { + called = true; + REQUIRE( a == 5 ); + REQUIRE( b == 5.4f ); // Danger + }); + + auto args_obj = std::make_tuple(5, 5.4f); + auto call_obj = std::make_tuple(0,0,"test1",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + p.mock_dispatchRPC(s, buf.str()); + REQUIRE( called ); + } + + SECTION("non-void bind no arguments") { + bool called = false; + + p.bind("test1", [&]() -> int { + called = true; + return 55; + }); + + auto args_obj = std::make_tuple(); + auto call_obj = std::make_tuple(0,0,"test1",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + p.mock_dispatchRPC(s, buf.str()); + REQUIRE( called ); + + // TODO Require that a writev occurred with result value + } +} + diff --git a/net/test/socket_unit.cpp b/net/test/socket_unit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8950335ba2217ac003d9847174cde463749f2e3a --- /dev/null +++ b/net/test/socket_unit.cpp @@ -0,0 +1,259 @@ +#include "catch.hpp" +#include <iostream> +#include <memory> +#include <map> + +#include <ftl/net/protocol.hpp> +#include <ftl/net/socket.hpp> + +using ftl::net::Socket; + +// --- Mock -------------------------------------------------------------------- + +class MockSocket : public Socket { + public: + MockSocket() : Socket(0) {} + void mock_data() { data(); } +}; + +static std::string last_rpc; +void ftl::net::Protocol::dispatchRPC(Socket &s, const std::string &d) { + last_rpc = d; + + // This should send a return value +} + +void ftl::net::Protocol::dispatchRaw(uint32_t service, Socket &s) { + +} + +ftl::net::Protocol::Protocol(uint64_t id) { +} + +ftl::net::Protocol::~Protocol() { +} + +ftl::net::Protocol *ftl::net::Protocol::find(uint64_t p) { + return NULL; +} + +// --- Support ----------------------------------------------------------------- + +static std::map<int, std::string> fakedata; + +void fake_send(int sd, uint32_t service, const std::string &data) { + //std::cout << "HEX SEND: " << hexStr(data) << std::endl; + char buf[8+data.size()]; + ftl::net::Header *h = (ftl::net::Header*)&buf; + h->size = data.size()+4; + h->service = service; + std::memcpy(&buf[8],data.data(),data.size()); + fakedata[sd] = std::string(&buf[0], 8+data.size()); + + //std::cout << "HEX SEND2: " << hexStr(fakedata[sd]) << std::endl; +} + +extern ssize_t recv(int sd, void *buf, size_t n, int f) { + if (fakedata.count(sd) == 0) { + std::cout << "Unrecognised socket" << std::endl; + return 0; + } + + int l = fakedata[sd].size(); + + std::memcpy(buf, fakedata[sd].c_str(), l); + fakedata.erase(sd); + return l; +} + +extern ssize_t writev(int sd, const struct iovec *v, int cnt) { + // TODO Use count incase more sources exist... + size_t len = v[0].iov_len+v[1].iov_len; + char buf[len]; + std::memcpy(&buf[0],v[0].iov_base,v[0].iov_len); + std::memcpy(&buf[v[0].iov_len],v[1].iov_base,v[1].iov_len); + fakedata[sd] = std::string(&buf[0], len); + return 0; +} + +static std::function<void()> waithandler; + +namespace ftl { +namespace net { +bool wait() { + if (waithandler) waithandler(); + //waithandler = nullptr; + return true; +} +}; +}; + +// --- Files to test ----------------------------------------------------------- + +#include "../src/socket.cpp" + +// --- Tests ------------------------------------------------------------------- + +using ftl::net::Protocol; + +TEST_CASE("Socket::call()", "[rpc]") { + MockSocket s; + + SECTION("no argument call") { + waithandler = [&]() { + // Read fakedata sent + // TODO Validate data + + // Do a fake send + auto res_obj = std::make_tuple(1,0,msgpack::object(),66); + std::stringstream buf; + msgpack::pack(buf, res_obj); + + fake_send(0, FTL_PROTOCOL_RPCRETURN, buf.str()); + s.mock_data(); + }; + + int res = s.call<int>("test1"); + + REQUIRE( res == 66 ); + } + + SECTION("one argument call") { + waithandler = [&]() { + // Read fakedata sent + // TODO Validate data + + // Do a fake send + auto res_obj = std::make_tuple(1,1,msgpack::object(),43); + std::stringstream buf; + msgpack::pack(buf, res_obj); + + fake_send(0, FTL_PROTOCOL_RPCRETURN, buf.str()); + s.mock_data(); + }; + + int res = s.call<int>("test1", 78); + + REQUIRE( res == 43 ); + } + + waithandler = nullptr; +} + +TEST_CASE("Socket receive RPC", "[rpc]") { + MockSocket s; + auto p = new Protocol(444); + s.setProtocol(p); + + SECTION("no argument call") { + // Do a fake send + auto args_obj = std::make_tuple(); + auto call_obj = std::make_tuple(0,0,"test1",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + fake_send(0, FTL_PROTOCOL_RPC, buf.str()); + s.mock_data(); // Force it to read the fake send... + + REQUIRE( last_rpc == buf.str() ); + } + + SECTION("one argument call") { + // Do a fake send + auto args_obj = std::make_tuple(55); + auto call_obj = std::make_tuple(0,0,"test2",args_obj); + std::stringstream buf; + msgpack::pack(buf, call_obj); + + fake_send(0, FTL_PROTOCOL_RPC, buf.str()); + s.mock_data(); // Force it to read the fake send... + + REQUIRE( last_rpc == buf.str() ); + } +} + +TEST_CASE("Socket::read()", "[io]") { + MockSocket s; + + SECTION("read an int") { + int i = 99; + fake_send(0, 100, std::string((char*)&i,4)); + + i = 0; + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.size() == sizeof(int) ); + REQUIRE( s.read(i) == sizeof(int) ); + REQUIRE( i == 99 ); + } + + SECTION("read two ints") { + int i[2]; + i[0] = 99; + i[1] = 101; + fake_send(0, 100, std::string((char*)&i,2*sizeof(int))); + + i[0] = 0; + i[1] = 0; + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.size() == 2*sizeof(int) ); + REQUIRE( s.read(&i,2) == 2*sizeof(int) ); + REQUIRE( i[0] == 99 ); + REQUIRE( i[1] == 101 ); + } + + SECTION("multiple reads") { + int i[2]; + i[0] = 99; + i[1] = 101; + fake_send(0, 100, std::string((char*)&i,2*sizeof(int))); + + i[0] = 0; + i[1] = 0; + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.read(&i[0],1) == sizeof(int) ); + REQUIRE( i[0] == 99 ); + REQUIRE( s.read(&i[1],1) == sizeof(int) ); + REQUIRE( i[1] == 101 ); + } + + SECTION("read a string") { + std::string str; + fake_send(0, 100, std::string("hello world")); + + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.size() == 11 ); + REQUIRE( s.read(str) == 11 ); + REQUIRE( str == "hello world" ); + } + + SECTION("read into existing string") { + std::string str; + str.reserve(11); + void *ptr = str.data(); + fake_send(0, 100, std::string("hello world")); + + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.size() == 11 ); + REQUIRE( s.read(str) == 11 ); + REQUIRE( str == "hello world" ); + REQUIRE( str.data() == ptr ); + } + + SECTION("read too much data") { + int i = 99; + fake_send(0, 100, std::string((char*)&i,4)); + + i = 0; + s.mock_data(); // Force a message read, but no protocol... + + REQUIRE( s.size() == sizeof(int) ); + REQUIRE( s.read(&i,2) == sizeof(int) ); + REQUIRE( i == 99 ); + } +} +