diff --git a/.gitignore b/.gitignore index c5ca081f079ab83aff2b6a136325d1ea45df6231..108f49f167c83fdde2b86280e21114ee2b9278a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ build **/config.cpp **/config.h _CPack_Packages -.vscode .env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ac5f2677d2092ac3d1d09767fff016ce45a2455..c7d969ce20ae4cef7b5bd7eda8783a0b49dc9509 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -64,6 +64,7 @@ "thread": "cpp", "typeinfo": "cpp", "valarray": "cpp", - "variant": "cpp" + "variant": "cpp", + "any": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 886295cf67665f6d06be520923d8a68ca01381e7..f8daf9f6956d648e5ebc926452687cda8be6f981 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ if (WIN32) # TODO(nick) Should do based upon compiler (VS) else() add_definitions(-DUNIX) # -fdiagnostics-color - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always -std=c++17 -fPIC -march=haswell -mavx2 -mfpmath=sse -Wall -Werror=unused-result -Werror=return-type") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always -std=c++17 -fPIC -march=haswell -mavx2 -mfpmath=sse -Wall -Werror") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3") diff --git a/include/ftl/exception.hpp b/include/ftl/exception.hpp index 8c140ef86e9182208154d563abc87ee8bc77edd2..ad002ea3a3a2be934cb67193933aca677e472b6e 100644 --- a/include/ftl/exception.hpp +++ b/include/ftl/exception.hpp @@ -7,70 +7,68 @@ #pragma once #include <sstream> +#include <string> namespace ftl { class Formatter { - public: - Formatter() {} - ~Formatter() {} - - template <typename Type> - inline Formatter & operator << (const Type & value) - { - stream_ << value; - return *this; - } - - inline std::string str() const { return stream_.str(); } - inline operator std::string () const { return stream_.str(); } - - enum ConvertToString - { - to_str - }; - inline std::string operator >> (ConvertToString) { return stream_.str(); } - -private: - std::stringstream stream_; - - Formatter(const Formatter &); - Formatter & operator = (Formatter &); + public: + Formatter() {} + ~Formatter() {} + + template <typename Type> + inline Formatter & operator << (const Type & value) { + stream_ << value; + return *this; + } + + inline std::string str() const { return stream_.str(); } + inline operator std::string () const { return stream_.str(); } + + enum ConvertToString { + to_str + }; + inline std::string operator >> (ConvertToString) { return stream_.str(); } + + private: + std::stringstream stream_; + + Formatter(const Formatter &); + Formatter & operator = (Formatter &); }; /** * Main FTL internal exception class. Use via Macro below. */ -class exception : public std::exception -{ - public: - explicit exception(const char *msg); - explicit exception(const Formatter &msg); - ~exception(); +class exception : public std::exception { + public: + explicit exception(const char *msg); + explicit exception(const Formatter &msg); + ~exception(); - const char* what() const throw () { - processed_ = true; - return msg_.c_str(); - } + const char* what() const throw() { + processed_ = true; + return msg_.c_str(); + } - std::string trace() const throw () { - return decode_backtrace(); - } + std::string trace() const throw() { + return decode_backtrace(); + } - void ignore() const { processed_ = true; } + void ignore() const { processed_ = true; } - private: - std::string decode_backtrace() const; + private: + std::string decode_backtrace() const; - std::string msg_; - mutable bool processed_; + std::string msg_; + mutable bool processed_; #ifdef __GNUC__ - static const int TRACE_SIZE_MAX_ = 16; - void* trace_[TRACE_SIZE_MAX_]; - int trace_size_; + static const int TRACE_SIZE_MAX_ = 16; + void* trace_[TRACE_SIZE_MAX_]; + int trace_size_; #endif }; -} +} // namespace ftl #define FTL_Error(A) (ftl::exception(ftl::Formatter() << A << " [" << __FILE__ << ":" << __LINE__ << "]")) diff --git a/include/ftl/handle.hpp b/include/ftl/handle.hpp index c30bae558a9f413d6ddd3421b9b5c30ce582da78..0744c914b6d15092585cdb5fdda1ef0f37bc44d7 100644 --- a/include/ftl/handle.hpp +++ b/include/ftl/handle.hpp @@ -6,24 +6,25 @@ #pragma once -#include <ftl/threads.hpp> -#include <ftl/exception.hpp> #include <functional> #include <unordered_map> +#include <utility> +#include <ftl/threads.hpp> +#include <ftl/exception.hpp> namespace ftl { struct Handle; struct BaseHandler { - virtual void remove(const Handle &)=0; + virtual void remove(const Handle &) = 0; - virtual void removeUnsafe(const Handle &)=0; + virtual void removeUnsafe(const Handle &) = 0; - inline Handle make_handle(BaseHandler*, int); + inline Handle make_handle(BaseHandler*, int); - protected: - std::mutex mutex_; - int id_=0; + protected: + std::mutex mutex_; + int id_ = 0; }; /** @@ -31,48 +32,58 @@ struct BaseHandler { * removed safely whenever the `Handle` instance is destroyed. */ struct [[nodiscard]] Handle { - friend struct BaseHandler; - - /** - * Cancel the callback and invalidate the handle. - */ - inline void cancel() { if (handler_) handler_->remove(*this); handler_ = nullptr; } - - inline void innerCancel() { if (handler_) handler_->removeUnsafe(*this); handler_ = nullptr; } - - inline int id() const { return id_; } - - Handle() : handler_(nullptr), id_(0) {} - - Handle(const Handle &)=delete; - Handle &operator=(const Handle &)=delete; - - inline Handle(Handle &&h) : handler_(nullptr) { - if (handler_) handler_->remove(*this); - handler_ = h.handler_; - h.handler_ = nullptr; - id_ = h.id_; - } - - inline Handle &operator=(Handle &&h) { - if (handler_) handler_->remove(*this); - handler_ = h.handler_; - h.handler_ = nullptr; - id_ = h.id_; - return *this; - } - - inline ~Handle() { - if (handler_) { - handler_->remove(*this); - } - } - - private: - BaseHandler *handler_; - int id_; - - Handle(BaseHandler *h, int id) : handler_(h), id_(id) {} + friend struct BaseHandler; + + /** + * Cancel the callback and invalidate the handle. + */ + inline void cancel() { + if (handler_) { + handler_->remove(*this); + } + handler_ = nullptr; + } + + inline void innerCancel() { + if (handler_) { + handler_->removeUnsafe(*this); + } + handler_ = nullptr; + } + + inline int id() const { return id_; } + + Handle() : handler_(nullptr), id_(0) {} + + Handle(const Handle &) = delete; + Handle &operator=(const Handle &) = delete; + + inline Handle(Handle &&h) : handler_(nullptr) { + if (handler_) handler_->remove(*this); + handler_ = h.handler_; + h.handler_ = nullptr; + id_ = h.id_; + } + + inline Handle &operator=(Handle &&h) { + if (handler_) handler_->remove(*this); + handler_ = h.handler_; + h.handler_ = nullptr; + id_ = h.id_; + return *this; + } + + inline ~Handle() { + if (handler_) { + handler_->remove(*this); + } + } + + private: + BaseHandler *handler_; + int id_; + + Handle(BaseHandler *h, int id) : handler_(h), id_(id) {} }; /** @@ -85,116 +96,118 @@ struct [[nodiscard]] Handle { */ template <typename ...ARGS> struct Handler : BaseHandler { - Handler() {} - ~Handler() { - // Ensure all thread pool jobs are done - while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); - } - - /** - * Add a new callback function. It returns a `Handle` object that must - * remain in scope, the destructor of the `Handle` will remove the callback. - */ - Handle on(const std::function<bool(ARGS...)> &f) { - std::unique_lock<std::mutex> lk(mutex_); - int id = id_++; - callbacks_[id] = f; - return make_handle(this, id); - } - - /** - * Safely trigger all callbacks. Note that `Handler` is locked when - * triggering so callbacks cannot make modifications to it or they will - * lock up. To remove a callback, return false from the callback, else - * return true. - */ - void trigger(ARGS ...args) { - bool hadFault = false; - std::unique_lock<std::mutex> lk(mutex_); - for (auto i=callbacks_.begin(); i!=callbacks_.end(); ) { - bool keep = true; - try { - keep = i->second(args...); - } catch(...) { - hadFault = true; - } - if (!keep) i = callbacks_.erase(i); - else ++i; - } - if (hadFault) throw FTL_Error("Callback exception"); - } - - /** - * Call all the callbacks in another thread. The callbacks are done in a - * single thread, not in parallel. - */ - void triggerAsync(ARGS ...args) { - ++jobs_; - ftl::pool.push([this, args...](int id) { - bool hadFault = false; - std::unique_lock<std::mutex> lk(mutex_); - for (auto i=callbacks_.begin(); i!=callbacks_.end(); ) { - bool keep = true; - try { - keep = i->second(args...); - } catch (...) { - hadFault = true; - } - if (!keep) i = callbacks_.erase(i); - else ++i; - } - --jobs_; - if (hadFault) throw FTL_Error("Callback exception"); - }); - } - - /** - * Each callback is called in its own thread job. Note: the return value - * of the callback is ignored in this case and does not allow callback - * removal via the return value. - */ - void triggerParallel(ARGS ...args) { - std::unique_lock<std::mutex> lk(mutex_); - jobs_ += callbacks_.size(); - for (auto i=callbacks_.begin(); i!=callbacks_.end(); ++i) { - ftl::pool.push([this, f = i->second, args...](int id) { - try { - f(args...); - } catch (const ftl::exception &e) { - --jobs_; - throw e; - } - --jobs_; - }); - } - } - - /** - * Remove a callback using its `Handle`. This is equivalent to allowing the - * `Handle` to be destroyed or cancelled. - */ - void remove(const Handle &h) override { - { - std::unique_lock<std::mutex> lk(mutex_); - callbacks_.erase(h.id()); - } - // Make sure any possible call to removed callback has finished. - while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); - } - - void removeUnsafe(const Handle &h) override { - callbacks_.erase(h.id()); - // Make sure any possible call to removed callback has finished. - while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); - } - - void clear() { - callbacks_.clear(); - } - - private: - std::unordered_map<int, std::function<bool(ARGS...)>> callbacks_; - std::atomic_int jobs_=0; + Handler() {} + ~Handler() { + // Ensure all thread pool jobs are done + while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + + /** + * Add a new callback function. It returns a `Handle` object that must + * remain in scope, the destructor of the `Handle` will remove the callback. + */ + Handle on(const std::function<bool(ARGS...)> &f) { + std::unique_lock<std::mutex> lk(mutex_); + int id = id_++; + callbacks_[id] = f; + return make_handle(this, id); + } + + /** + * Safely trigger all callbacks. Note that `Handler` is locked when + * triggering so callbacks cannot make modifications to it or they will + * lock up. To remove a callback, return false from the callback, else + * return true. + */ + void trigger(ARGS ...args) { + bool hadFault = false; + std::unique_lock<std::mutex> lk(mutex_); + for (auto i = callbacks_.begin(); i != callbacks_.end(); ) { + bool keep = true; + try { + keep = i->second(args...); + } catch(...) { + hadFault = true; + } + if (!keep) i = callbacks_.erase(i); + else + ++i; + } + if (hadFault) throw FTL_Error("Callback exception"); + } + + /** + * Call all the callbacks in another thread. The callbacks are done in a + * single thread, not in parallel. + */ + void triggerAsync(ARGS ...args) { + ++jobs_; + ftl::pool.push([this, args...](int id) { + bool hadFault = false; + std::unique_lock<std::mutex> lk(mutex_); + for (auto i = callbacks_.begin(); i != callbacks_.end(); ) { + bool keep = true; + try { + keep = i->second(args...); + } catch (...) { + hadFault = true; + } + if (!keep) i = callbacks_.erase(i); + else + ++i; + } + --jobs_; + if (hadFault) throw FTL_Error("Callback exception"); + }); + } + + /** + * Each callback is called in its own thread job. Note: the return value + * of the callback is ignored in this case and does not allow callback + * removal via the return value. + */ + void triggerParallel(ARGS ...args) { + std::unique_lock<std::mutex> lk(mutex_); + jobs_ += callbacks_.size(); + for (auto i = callbacks_.begin(); i != callbacks_.end(); ++i) { + ftl::pool.push([this, f = i->second, args...](int id) { + try { + f(args...); + } catch (const ftl::exception &e) { + --jobs_; + throw e; + } + --jobs_; + }); + } + } + + /** + * Remove a callback using its `Handle`. This is equivalent to allowing the + * `Handle` to be destroyed or cancelled. + */ + void remove(const Handle &h) override { + { + std::unique_lock<std::mutex> lk(mutex_); + callbacks_.erase(h.id()); + } + // Make sure any possible call to removed callback has finished. + while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + + void removeUnsafe(const Handle &h) override { + callbacks_.erase(h.id()); + // Make sure any possible call to removed callback has finished. + while (jobs_ > 0 && ftl::pool.size() > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + + void clear() { + callbacks_.clear(); + } + + private: + std::unordered_map<int, std::function<bool(ARGS...)>> callbacks_; + std::atomic_int jobs_ = 0; }; /** @@ -205,61 +218,58 @@ struct Handler : BaseHandler { */ template <typename ...ARGS> struct SingletonHandler : BaseHandler { - /** - * Add a new callback function. It returns a `Handle` object that must - * remain in scope, the destructor of the `Handle` will remove the callback. - */ - [[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) { - std::unique_lock<std::mutex> lk(mutex_); - if (callback_) throw FTL_Error("Callback already bound"); - callback_ = f; - return make_handle(this, id_++); - } - - /** - * Safely trigger all callbacks. Note that `Handler` is locked when - * triggering so callbacks cannot make modifications to it or they will - * lock up. To remove a callback, return false from the callback, else - * return true. - */ - bool trigger(ARGS ...args) { - std::unique_lock<std::mutex> lk(mutex_); - if (callback_) { - bool keep = callback_(std::forward<ARGS>(args)...); - if (!keep) callback_ = nullptr; - return keep; - } else { - return false; - } - //} catch (const std::exception &e) { - // LOG(ERROR) << "Exception in callback: " << e.what(); - //} - } - - /** - * Remove a callback using its `Handle`. This is equivalent to allowing the - * `Handle` to be destroyed or cancelled. If the handle does not match the - * currently bound callback then the callback is not removed. - */ - void remove(const Handle &h) override { - std::unique_lock<std::mutex> lk(mutex_); - if (h.id() == id_-1) callback_ = nullptr; - } - - void removeUnsafe(const Handle &h) override { - if (h.id() == id_-1) callback_ = nullptr; - } - - void reset() { callback_ = nullptr; } - - operator bool() const { return (bool)callback_; } - - private: - std::function<bool(ARGS...)> callback_; + /** + * Add a new callback function. It returns a `Handle` object that must + * remain in scope, the destructor of the `Handle` will remove the callback. + */ + [[nodiscard]] Handle on(const std::function<bool(ARGS...)> &f) { + std::unique_lock<std::mutex> lk(mutex_); + if (callback_) throw FTL_Error("Callback already bound"); + callback_ = f; + return make_handle(this, id_++); + } + + /** + * Safely trigger all callbacks. Note that `Handler` is locked when + * triggering so callbacks cannot make modifications to it or they will + * lock up. To remove a callback, return false from the callback, else + * return true. + */ + bool trigger(ARGS ...args) { + std::unique_lock<std::mutex> lk(mutex_); + if (callback_) { + bool keep = callback_(std::forward<ARGS>(args)...); + if (!keep) callback_ = nullptr; + return keep; + } else { + return false; + } + } + + /** + * Remove a callback using its `Handle`. This is equivalent to allowing the + * `Handle` to be destroyed or cancelled. If the handle does not match the + * currently bound callback then the callback is not removed. + */ + void remove(const Handle &h) override { + std::unique_lock<std::mutex> lk(mutex_); + if (h.id() == id_-1) callback_ = nullptr; + } + + void removeUnsafe(const Handle &h) override { + if (h.id() == id_-1) callback_ = nullptr; + } + + void reset() { callback_ = nullptr; } + + operator bool() const { return static_cast<bool>(callback_); } + + private: + std::function<bool(ARGS...)> callback_; }; -} +} // namespace ftl ftl::Handle ftl::BaseHandler::make_handle(BaseHandler *h, int id) { - return ftl::Handle(h, id); + return ftl::Handle(h, id); } diff --git a/include/ftl/protocol.hpp b/include/ftl/protocol.hpp index 0244c66becea5e0589fce59b32c5e17639120c07..196cadeb85a72d910cd1f2efb44ec347e980f5ba 100644 --- a/include/ftl/protocol.hpp +++ b/include/ftl/protocol.hpp @@ -7,6 +7,7 @@ #pragma once #include <memory> +#include <string> #include <ftl/uuid.hpp> namespace ftl { @@ -20,13 +21,65 @@ class Service; void reset(); extern ftl::UUID id; -} +} // namespace protocol +/** + * @brief Get the Self object. This may initialise the internal system when + * first called. A Self object allows for the overall control of the network + * and general RPC functionality between hosts. If this is called multiple + * times then the same internal object is returned, it is a singleton. + * + * @return std::shared_ptr<ftl::protocol::Self> + */ std::shared_ptr<ftl::protocol::Self> getSelf(); + +/** + * @brief Create a secondary Self object. Mostly for testing purposes, this + * allows additional instances to be created of the otherwise singleton class. + * + * @return std::shared_ptr<ftl::protocol::Self> + */ std::shared_ptr<ftl::protocol::Self> createDummySelf(); + +/** + * @brief Set the web service URI to use. There should be a single connection + * to a web service that provides additional management functionality beyond + * a typical node. By calling this function the system is informed about where + * to ask about certain resources. + * + * @param uri A websocket URI, either WS or WSS protocol. + * @return A node instance for the service + */ std::shared_ptr<ftl::protocol::Service> setServiceProvider(const std::string &uri); + +/** + * @brief Connect to another machine. This uses the singleton Self instance, however, + * it is possible to also connect from another secondary Self instance by + * using a member function. + * + * @param uri A TCP URI with the address and port of another machine. + * @return std::shared_ptr<ftl::protocol::Node> + */ std::shared_ptr<ftl::protocol::Node> connectNode(const std::string &uri); + +/** + * @brief Host a new stream. The URI must be either a file or an FTL protocol. + * A file stream opened by this function will be write only, and a network + * stream will broadcast itself as a newly available source. + * + * @param uri Either file:// or ftl:// + * @return std::shared_ptr<ftl::protocol::Stream> + */ std::shared_ptr<ftl::protocol::Stream> createStream(const std::string &uri); + +/** + * @brief Open an existing stream. This can be a file or a network stream. + * A file stream will be opened readonly, and a network stream will attempt + * to find the stream on the local network or using the web service. + * + * @param uri Either file:// or ftl:// + * @return std::shared_ptr<ftl::protocol::Stream> + */ std::shared_ptr<ftl::protocol::Stream> getStream(const std::string &uri); -} +} // namespace ftl diff --git a/include/ftl/protocol/broadcaster.hpp b/include/ftl/protocol/broadcaster.hpp index 4e404579b4b116dafb04350c2c435c86e8ededbc..ec768bc96e495dd7a9e952ac0171a94c81caa2ed 100644 --- a/include/ftl/protocol/broadcaster.hpp +++ b/include/ftl/protocol/broadcaster.hpp @@ -1,7 +1,14 @@ +/** + * @file broadcaster.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once -#include <ftl/protocol/streams.hpp> #include <list> +#include <memory> +#include <ftl/protocol/streams.hpp> namespace ftl { namespace protocol { @@ -12,50 +19,49 @@ namespace protocol { * packets. */ class Broadcast : public Stream { - public: - explicit Broadcast(); - virtual ~Broadcast(); - - void add(const std::shared_ptr<Stream> &); - void remove(const std::shared_ptr<Stream> &); - void clear(); + public: + Broadcast(); + virtual ~Broadcast(); - bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; + void add(const std::shared_ptr<Stream> &); + void remove(const std::shared_ptr<Stream> &); + void clear(); - bool begin() override; - bool end() override; - bool active() override; + bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; - void reset() override; + bool begin() override; + bool end() override; + bool active() override; - void refresh() override; + void reset() override; - std::list<std::shared_ptr<Stream>> streams() const; + void refresh() override; - void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; + std::list<std::shared_ptr<Stream>> streams() const; - std::any getProperty(ftl::protocol::StreamProperty opt) override; + void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; - bool supportsProperty(ftl::protocol::StreamProperty opt) override; + std::any getProperty(ftl::protocol::StreamProperty opt) override; - bool enable(FrameID id) override; + bool supportsProperty(ftl::protocol::StreamProperty opt) override; - bool enable(FrameID id, ftl::protocol::Channel channel) override; + bool enable(FrameID id) override; - bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; + bool enable(FrameID id, ftl::protocol::Channel channel) override; - StreamType type() const override; + bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; - private: + StreamType type() const override; - struct StreamEntry { - std::shared_ptr<Stream> stream; - ftl::Handle handle; - ftl::Handle req_handle; - ftl::Handle avail_handle; - }; + private: + struct StreamEntry { + std::shared_ptr<Stream> stream; + ftl::Handle handle; + ftl::Handle req_handle; + ftl::Handle avail_handle; + }; - std::list<StreamEntry> streams_; + std::list<StreamEntry> streams_; }; } diff --git a/include/ftl/protocol/channelSet.hpp b/include/ftl/protocol/channelSet.hpp index 2f2485d05d7a8a70377f10e4301376ce2aa14485..8d0a7220085d229ff00141490aa5c28ea4954e35 100644 --- a/include/ftl/protocol/channelSet.hpp +++ b/include/ftl/protocol/channelSet.hpp @@ -6,8 +6,8 @@ #pragma once -#include <ftl/protocol/channels.hpp> #include <unordered_set> +#include <ftl/protocol/channels.hpp> namespace ftl { namespace protocol { @@ -20,26 +20,26 @@ ftl::protocol::ChannelSet operator&(const ftl::protocol::ChannelSet &a, const ft ftl::protocol::ChannelSet operator-(const ftl::protocol::ChannelSet &a, const ftl::protocol::ChannelSet &b); inline ftl::protocol::ChannelSet &operator+=(ftl::protocol::ChannelSet &t, ftl::protocol::Channel c) { - t.insert(c); - return t; + t.insert(c); + return t; } inline ftl::protocol::ChannelSet &operator-=(ftl::protocol::ChannelSet &t, ftl::protocol::Channel c) { - t.erase(c); - return t; + t.erase(c); + return t; } inline ftl::protocol::ChannelSet operator+(const ftl::protocol::ChannelSet &t, ftl::protocol::Channel c) { - auto r = t; - r.insert(c); - return r; + auto r = t; + r.insert(c); + return r; } inline ftl::protocol::ChannelSet operator+(ftl::protocol::Channel a, ftl::protocol::Channel b) { - std::unordered_set<ftl::protocol::Channel> r; - r.insert(a); - r.insert(b); - return r; + std::unordered_set<ftl::protocol::Channel> r; + r.insert(a); + r.insert(b); + return r; } bool operator!=(const ftl::protocol::ChannelSet &a, const ftl::protocol::ChannelSet &b); diff --git a/include/ftl/protocol/channelUtils.hpp b/include/ftl/protocol/channelUtils.hpp index 218ea6ff78eefd4dad395a97953f58505a8d3a99..4b33794f80bd18919f821b293c1c180dcb5f4a6c 100644 --- a/include/ftl/protocol/channelUtils.hpp +++ b/include/ftl/protocol/channelUtils.hpp @@ -1,13 +1,20 @@ +/** + * @file channelUtils.hpp + * @copyright Copyright (c) 2020 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once +#include <string> #include <ftl/protocol/channels.hpp> namespace ftl { namespace protocol { -inline bool isVideo(Channel c) { return (int)c < 32; }; -inline bool isAudio(Channel c) { return (int)c >= 32 && (int)c < 64; }; -inline bool isData(Channel c) { return (int)c >= 64; }; +inline bool isVideo(Channel c) { return static_cast<int>(c) < 32; } +inline bool isAudio(Channel c) { return static_cast<int>(c) >= 32 && static_cast<int>(c) < 64; } +inline bool isData(Channel c) { return static_cast<int>(c) >= 64; } /** Obtain a string name for channel. */ std::string name(Channel c); @@ -17,17 +24,16 @@ int type(Channel c); /** @deprecated */ inline bool isFloatChannel(ftl::codecs::Channel chan) { - switch (chan) { - case Channel::GroundTruth: - case Channel::Depth : - //case Channel::Normals : - case Channel::Confidence: - case Channel::Flow : - case Channel::Density: - case Channel::Energy : return true; - default : return false; - } + switch (chan) { + case Channel::GroundTruth : + case Channel::Depth : + case Channel::Confidence : + case Channel::Flow : + case Channel::Density : + case Channel::Energy : return true; + default : return false; + } } -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/channels.hpp b/include/ftl/protocol/channels.hpp index 5dc74bccf3d01f3fea8cbac077d391762b743b1b..49a4e1763e41c230b868eaa4675348c2d8a818e7 100644 --- a/include/ftl/protocol/channels.hpp +++ b/include/ftl/protocol/channels.hpp @@ -7,129 +7,126 @@ #pragma once #include <bitset> -//#include <msgpack.hpp> namespace ftl { namespace protocol { /** Frame channel identifier. */ enum struct Channel : int { - /* Video Channels */ - kNone = -1, - kColour = 0, // 8UC3 or 8UC4 - kLeft = 0, - kDepth = 1, // 32S or 32F - kRight = 2, // 8UC3 or 8UC4 - kColour2 = 2, - kDepth2 = 3, - kDeviation = 4, - kScreen = 4, // 16SC2 - kNormals = 5, // 16FC4 - kWeights = 6, // short - kConfidence = 7, // 32F - kContribution = 7, // 32F - kEnergyVector = 8, // 32FC4 - kFlow = 9, // 16SC2 - kFlow2 = 10, // 16SC2 - kEnergy = 10, // 32F - kMask = 11, // 32U - kDensity = 12, // 32F - kSupport1 = 13, // 8UC4 (currently) - kSupport2 = 14, // 8UC4 (currently) - kSegmentation = 15, // 32S? - kNormals2 = 16, // 16FC4 - kUNUSED1 = 17, - kDisparity = 18, - kSmoothing = 19, // 32F - kUNUSED2 = 20, - kOverlay = 21, // 8UC4 - kGroundTruth = 22, // 32F - - /* Audio Channels */ - kAudioMono = 32, // Deprecated, will always be stereo - kAudioStereo = 33, - kAudio = 33, - - /* Special data channels */ - kConfiguration = 64, // JSON Data - kSettings1 = 65, - kCalibration = 65, // Camera Parameters Object - kPose = 66, // Eigen::Matrix4d, camera transform - kSettings2 = 67, - kCalibration2 = 67, // Right camera parameters - kIndex = 68, - kControl = 69, // For stream and encoder control - kSettings3 = 70, - kMetaData = 71, // Map of string pairs (key, value) - kCapabilities = 72, // Unordered set of int capabilities - kCalibrationData = 73, // Just for stereo intrinsics/extrinsics etc - kThumbnail = 74, // Small JPG thumbnail, sometimes updated - kOverlaySelect = 75, // Choose what to have in the overlay channel - kStartTime = 76, // Stream start timestamp - kUser = 77, // User currently controlling the stream - - kAccelerometer = 90, // Eigen::Vector3f - kGyroscope = 91, // Eigen::Vector3f - - /* Camera Options */ - kBrightness = 100, - kContrast = 101, - kExposure = 102, - kGain = 103, - kWhiteBalance = 104, - kAutoExposure = 105, - kAutoWhiteBalance = 106, - kCameraTemperature = 107, - - /* Realsense Options */ - kRS2_LaserPower = 150, - kRS2_MinDistance = 151, - kRS2_MaxDistance = 152, - kRS2_InterCamSync = 153, - kRS2_PostSharpening = 154, - - /* Pylon Options 200 */ - - /* Audio Settings 300 */ - - /* Renderer Settings 400 */ - kRenderer_CameraType = 400, // stereo, normal, tile - kRenderer_Visualisation = 401, // Pointcloud, mesh, other - kRenderer_Engine = 402, // OpenGL, CUDA, other - kRenderer_FPS = 403, // Frames per second - kRenderer_View = 404, // Fixed viewpoint to one source - kRenderer_Channel = 405, // Select overlay channel, - kRenderer_Opacity = 406, // Opacity of overlay channel - kRenderer_Sources = 407, // Which source devices to use - kRenderer_Projection = 408, // 0 = normal, 1 = ortho, 2 = equirect - kRenderer_Background = 409, // Background colour - kRenderer_ShowBadColour = 420, - kRenderer_CoolEffect = 421, - kRenderer_EffectColour = 422, - kRenderer_ShowColourWeights = 423, - kRenderer_TriangleLimit = 424, - kRenderer_DisconDisparities = 425, - kRenderer_NormalWeightColour = 426, - kRenderer_ChannelWeights = 427, - kRenderer_AccumFunc = 428, - - /* Pipeline Settings */ - kPipeline_Enable = 500, - kPipeline_EnableMVMLS = 501, - kPipeline_EnableAruco = 502, - - /* Custom / user data channels */ - kData = 2048, // Do not use - kEndFrame = 2048, // Signify the last packet - kFaces = 2049, // Data about detected faces - kTransforms = 2050, // Transformation matrices for framesets - kShapes3D = 2051, // Labeled 3D shapes - kMessages = 2052, // Vector of Strings - kTouch = 2053, // List of touch data type (each touch point) - kPipelines = 2054, // List of pipline URIs that have been applied + /* Video Channels */ + kNone = -1, + kColour = 0, // 8UC3 or 8UC4 + kLeft = 0, + kDepth = 1, // 32S or 32F + kRight = 2, // 8UC3 or 8UC4 + kColour2 = 2, + kDepth2 = 3, + kDeviation = 4, + kScreen = 4, // 16SC2 + kNormals = 5, // 16FC4 + kWeights = 6, // short + kConfidence = 7, // 32F + kContribution = 7, // 32F + kEnergyVector = 8, // 32FC4 + kFlow = 9, // 16SC2 + kFlow2 = 10, // 16SC2 + kEnergy = 10, // 32F + kMask = 11, // 32U + kDensity = 12, // 32F + kSupport1 = 13, // 8UC4 (currently) + kSupport2 = 14, // 8UC4 (currently) + kSegmentation = 15, // 32S? + kNormals2 = 16, // 16FC4 + kUNUSED1 = 17, + kDisparity = 18, + kSmoothing = 19, // 32F + kUNUSED2 = 20, + kOverlay = 21, // 8UC4 + kGroundTruth = 22, // 32F + + /* Audio Channels */ + kAudioMono = 32, // Deprecated, will always be stereo + kAudioStereo = 33, + kAudio = 33, + + /* Special data channels */ + kConfiguration = 64, // JSON Data + kSettings1 = 65, + kCalibration = 65, // Camera Parameters Object + kPose = 66, // Eigen::Matrix4d, camera transform + kSettings2 = 67, + kCalibration2 = 67, // Right camera parameters + kIndex = 68, + kControl = 69, // For stream and encoder control + kSettings3 = 70, + kMetaData = 71, // Map of string pairs (key, value) + kCapabilities = 72, // Unordered set of int capabilities + kCalibrationData = 73, // Just for stereo intrinsics/extrinsics etc + kThumbnail = 74, // Small JPG thumbnail, sometimes updated + kOverlaySelect = 75, // Choose what to have in the overlay channel + kStartTime = 76, // Stream start timestamp + kUser = 77, // User currently controlling the stream + + kAccelerometer = 90, // Eigen::Vector3f + kGyroscope = 91, // Eigen::Vector3f + + /* Camera Options */ + kBrightness = 100, + kContrast = 101, + kExposure = 102, + kGain = 103, + kWhiteBalance = 104, + kAutoExposure = 105, + kAutoWhiteBalance = 106, + kCameraTemperature = 107, + + /* Realsense Options */ + kRS2_LaserPower = 150, + kRS2_MinDistance = 151, + kRS2_MaxDistance = 152, + kRS2_InterCamSync = 153, + kRS2_PostSharpening = 154, + + /* Pylon Options 200 */ + + /* Audio Settings 300 */ + + /* Renderer Settings 400 */ + kRenderer_CameraType = 400, // stereo, normal, tile + kRenderer_Visualisation = 401, // Pointcloud, mesh, other + kRenderer_Engine = 402, // OpenGL, CUDA, other + kRenderer_FPS = 403, // Frames per second + kRenderer_View = 404, // Fixed viewpoint to one source + kRenderer_Channel = 405, // Select overlay channel, + kRenderer_Opacity = 406, // Opacity of overlay channel + kRenderer_Sources = 407, // Which source devices to use + kRenderer_Projection = 408, // 0 = normal, 1 = ortho, 2 = equirect + kRenderer_Background = 409, // Background colour + kRenderer_ShowBadColour = 420, + kRenderer_CoolEffect = 421, + kRenderer_EffectColour = 422, + kRenderer_ShowColourWeights = 423, + kRenderer_TriangleLimit = 424, + kRenderer_DisconDisparities = 425, + kRenderer_NormalWeightColour = 426, + kRenderer_ChannelWeights = 427, + kRenderer_AccumFunc = 428, + + /* Pipeline Settings */ + kPipeline_Enable = 500, + kPipeline_EnableMVMLS = 501, + kPipeline_EnableAruco = 502, + + /* Custom / user data channels */ + kData = 2048, // Do not use + kEndFrame = 2048, // Signify the last packet + kFaces = 2049, // Data about detected faces + kTransforms = 2050, // Transformation matrices for framesets + kShapes3D = 2051, // Labeled 3D shapes + kMessages = 2052, // Vector of Strings + kTouch = 2053, // List of touch data type (each touch point) + kPipelines = 2054, // List of pipline URIs that have been applied }; -} -} - -//MSGPACK_ADD_ENUM(ftl::codecs::Channel); +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/codecs.hpp b/include/ftl/protocol/codecs.hpp index 53556443099bc0135759458d0faece86e438112a..67762b2087cac624da53a3a6530ab8b606361811 100644 --- a/include/ftl/protocol/codecs.hpp +++ b/include/ftl/protocol/codecs.hpp @@ -7,7 +7,7 @@ #pragma once #include <cstdint> -//#include <msgpack.hpp> +#include <utility> namespace ftl { @@ -17,42 +17,40 @@ namespace ftl { */ namespace protocol { -static constexpr uint8_t kFlagRequest = 0x01; ///< Used for empty data packets to mark a request for data -static constexpr uint8_t kFlagCompleted = 0x02; ///< Last packet for timestamp +static constexpr uint8_t kFlagRequest = 0x01; ///< Used for empty data packets to mark a request for data +static constexpr uint8_t kFlagCompleted = 0x02; ///< Last packet for timestamp static constexpr uint8_t kFlagReset = 0x04; /** * Compression format used. */ enum struct Codec : uint8_t { - /* Video (image) codecs */ - kJPG = 0, - kPNG, - kH264, - kHEVC, // H265 - kH264Lossless, - kHEVCLossLess, - - /* Audio codecs */ - kWave=32, - kOPUS, - - /* Data "codecs" */ - kJSON = 100, // A JSON string - kCalibration, // Camera parameters object [deprecated] - kPose, // 4x4 eigen matrix [deprecated] - kMsgPack, - kString, // Null terminated string - kRaw, // Some unknown binary format - - kInvalid = 254, - kAny = 255 + /* Video (image) codecs */ + kJPG = 0, + kPNG, + kH264, + kHEVC, // H265 + kH264Lossless, + kHEVCLossLess, + + /* Audio codecs */ + kWave = 32, + kOPUS, + + /* Data "codecs" */ + kJSON = 100, // A JSON string + kCalibration, // Camera parameters object [deprecated] + kPose, // 4x4 eigen matrix [deprecated] + kMsgPack, + kString, // Null terminated string + kRaw, // Some unknown binary format + + kInvalid = 254, + kAny = 255 }; /** Given a frame count, return a width x height tile configuration. */ -std::pair<int,int> chooseTileConfig(int size); +std::pair<int, int> chooseTileConfig(int size); -} // namespace codecs +} // namespace protocol } // namespace ftl - -//MSGPACK_ADD_ENUM(ftl::codecs::codec_t); diff --git a/include/ftl/protocol/error.hpp b/include/ftl/protocol/error.hpp index 708ed388ea73330d85b1c9fae3b5dc372ce05b34..3f172027407e88c485754fb66dda2f37a0b24437 100644 --- a/include/ftl/protocol/error.hpp +++ b/include/ftl/protocol/error.hpp @@ -1,8 +1,18 @@ +/** + * @file error.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once namespace ftl { namespace protocol { +/** + * @brief Error codes for asynchronous error events. + * + */ enum struct Error { kNoError = 0, kUnknown = 1, @@ -22,5 +32,5 @@ enum struct Error { kBadURI }; -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/frameid.hpp b/include/ftl/protocol/frameid.hpp index 39610ca3e63f7224e0d0ef27642f8c2c966c2d9e..643b1a1e8a1f012478f6fdb765f9bbe5bcc64962 100644 --- a/include/ftl/protocol/frameid.hpp +++ b/include/ftl/protocol/frameid.hpp @@ -1,3 +1,9 @@ +/** + * @file frameid.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once #include <cinttypes> @@ -11,45 +17,46 @@ namespace protocol { * frames cannot be duplicated. */ struct FrameID { - uint32_t id; - - /** - * Frameset ID for this frame. - */ - inline unsigned int frameset() const { return id >> 8; } - - /** - * Frame index within the frameset. This will correspond to the vector - * index in the frameset object. - */ - inline unsigned int source() const { return id & 0xff; } - - /** - * The packed int with both frameset ID and index. - */ - operator uint32_t() const { return id; } - - inline FrameID &operator=(int v) { id = v; return *this; } - - /** - * Create a frame ID using a frameset id and a source number. - * @param fs Frameset id - * @param s Source number inside frameset - */ - FrameID(unsigned int fs, unsigned int s) : id((fs << 8) + (s & 0xff) ) {} - explicit FrameID(uint32_t x) : id(x) {} - FrameID() : id(0) {} + uint32_t id; + + /** + * Frameset ID for this frame. + */ + inline unsigned int frameset() const { return id >> 8; } + + /** + * Frame index within the frameset. This will correspond to the vector + * index in the frameset object. + */ + inline unsigned int source() const { return id & 0xff; } + + /** + * The packed int with both frameset ID and index. + */ + operator uint32_t() const { return id; } + + inline FrameID &operator=(int v) { + id = v; + return *this; + } + + /** + * Create a frame ID using a frameset id and a source number. + * @param fs Frameset id + * @param s Source number inside frameset + */ + FrameID(unsigned int fs, unsigned int s) : id((fs << 8) + (s & 0xff) ) {} + explicit FrameID(uint32_t x) : id(x) {} + FrameID() : id(0) {} }; -} -} +} // namespace protocol +} // namespace ftl // custom specialization of std::hash can be injected in namespace std template<> -struct std::hash<ftl::protocol::FrameID> -{ - std::size_t operator()(ftl::protocol::FrameID const& s) const noexcept - { +struct std::hash<ftl::protocol::FrameID> { + std::size_t operator()(ftl::protocol::FrameID const& s) const noexcept { return std::hash<unsigned int>{}(s.id); } }; diff --git a/include/ftl/protocol/muxer.hpp b/include/ftl/protocol/muxer.hpp index 777c276e61e1c9c59af6e0a7f66f1a4e17d092e7..5d934b3ff73e47a74bd9587d7c15f34c342fea98 100644 --- a/include/ftl/protocol/muxer.hpp +++ b/include/ftl/protocol/muxer.hpp @@ -1,9 +1,17 @@ -#pragma once +/** + * @file muxer.hpp + * @copyright Copyright (c) 2020 University of Turku, MIT License + * @author Nicolas Pope + */ -#include <ftl/protocol/streams.hpp> +#pragma once #include <map> #include <list> +#include <memory> +#include <unordered_map> +#include <utility> +#include <ftl/protocol/streams.hpp> namespace ftl { namespace protocol { @@ -17,65 +25,68 @@ static constexpr size_t kMaxStreams = 5; * stream mapping to be registered. */ class Muxer : public Stream { - public: - explicit Muxer(); - virtual ~Muxer(); - - void add(const std::shared_ptr<Stream> &, int fsid=-1); - void remove(const std::shared_ptr<Stream> &); + public: + Muxer(); + virtual ~Muxer(); - //bool onPacket(const StreamCallback &) override; + void add(const std::shared_ptr<Stream> &, int fsid = -1); + void remove(const std::shared_ptr<Stream> &); - bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; + bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; - bool begin() override; - bool end() override; - bool active() override; + bool begin() override; + bool end() override; + bool active() override; - void reset() override; + void reset() override; - bool enable(FrameID id) override; + bool enable(FrameID id) override; - bool enable(FrameID id, ftl::protocol::Channel channel) override; + bool enable(FrameID id, ftl::protocol::Channel channel) override; - bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; + bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; - void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; + void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; - std::any getProperty(ftl::protocol::StreamProperty opt) override; + std::any getProperty(ftl::protocol::StreamProperty opt) override; - bool supportsProperty(ftl::protocol::StreamProperty opt) override; + bool supportsProperty(ftl::protocol::StreamProperty opt) override; - StreamType type() const override; + StreamType type() const override; - std::shared_ptr<Stream> originStream(FrameID) const; + /** + * @brief Get the stream instance associated with an ID. + * + * @return std::shared_ptr<Stream> + */ + std::shared_ptr<Stream> originStream(FrameID) const; - private: - struct StreamEntry { - std::shared_ptr<Stream> stream; - ftl::Handle handle; - ftl::Handle req_handle; - ftl::Handle avail_handle; - ftl::Handle err_handle; - int id = 0; - int fixed_fs = -1; - }; + private: + struct StreamEntry { + std::shared_ptr<Stream> stream; + ftl::Handle handle; + ftl::Handle req_handle; + ftl::Handle avail_handle; + ftl::Handle err_handle; + int id = 0; + int fixed_fs = -1; + }; - std::unordered_map<int, int> fsmap_; - std::unordered_map<int, int> sourcecount_; - std::unordered_map<int64_t, FrameID> imap_; - std::unordered_map<FrameID, std::pair<FrameID, Muxer::StreamEntry*>> omap_; - std::list<StreamEntry> streams_; - mutable SHARED_MUTEX mutex_; - std::atomic_int stream_ids_ = 0; - std::atomic_int framesets_ = 0; + std::unordered_map<int, int> fsmap_; + std::unordered_map<int, int> sourcecount_; + std::unordered_map<int64_t, FrameID> imap_; + std::unordered_map<FrameID, std::pair<FrameID, Muxer::StreamEntry*>> omap_; + std::list<StreamEntry> streams_; + mutable SHARED_MUTEX mutex_; + std::atomic_int stream_ids_ = 0; + std::atomic_int framesets_ = 0; - /* On packet receive, map to local ID */ - FrameID _mapFromInput(StreamEntry *, FrameID id); + /* On packet receive, map to local ID */ + FrameID _mapFromInput(StreamEntry *, FrameID id); - /* On posting, map to output ID */ - std::pair<FrameID, StreamEntry*> _mapToOutput(FrameID id) const; + /* On posting, map to output ID */ + std::pair<FrameID, StreamEntry*> _mapToOutput(FrameID id) const; }; -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/node.hpp b/include/ftl/protocol/node.hpp index e34278ebc9cb1bde0f5a5fc0966321c88e2e5be4..b9b3a1deaf8127eb1d7cda0161d50af6c877e988 100644 --- a/include/ftl/protocol/node.hpp +++ b/include/ftl/protocol/node.hpp @@ -6,9 +6,9 @@ #pragma once -#include <ftl/uuid.hpp> - #include <memory> +#include <string> +#include <ftl/uuid.hpp> namespace ftl { namespace net { @@ -24,94 +24,96 @@ enum struct NodeType { }; enum struct NodeStatus { - kInvalid, // no socket - kConnecting, // socket created, no handshake yet - kConnected, // connection fully established - kDisconnected, // socket closed, reconnect not possible - kReconnecting // socket closed, call reconnect() to try reconnecting + kInvalid, // no socket + kConnecting, // socket created, no handshake yet + kConnected, // connection fully established + kDisconnected, // socket closed, reconnect not possible + kReconnecting // socket closed, call reconnect() to try reconnecting }; /** - * To be constructed using the Universe::connect() method and not to be - * created directly. + * @brief An API wrapper for a network connection. This object provides the + * available RPC calls and connection status or control methods. Note that + * releasing the shared pointer will not result in connection termination, + * it must be closed and then released for the garbage collection to happen. + * */ -class Node { - public: - /** Peer for outgoing connection: resolve address and connect */ - explicit Node(const ftl::net::PeerPtr &impl); - virtual ~Node(); - - /** - * Close the peer if open. Setting retry parameter to true will initiate - * backoff retry attempts. This is used to deliberately close a connection - * and not for error conditions where different close semantics apply. - * - * @param retry Should reconnection be attempted? - */ - void close(bool retry=false); - - bool isConnected() const; - /** - * Block until the connection and handshake has completed. You should use - * onConnect callbacks instead of blocking, mostly this is intended for - * the unit tests to keep them synchronous. - * - * @return True if all connections were successful, false if timeout or error. - */ - bool waitConnection(int seconds = 1); - - /** - * Make a reconnect attempt. Called internally by Universe object. - */ - bool reconnect(); - - bool isOutgoing() const; - - /** - * Test if the connection is valid. This returns true in all conditions - * except where the socket has been disconnected permenantly, or was never - * able to connect, perhaps due to an invalid address, or is in middle of a - * reconnect attempt. (Valid states: kConnecting, kConnected) - * - * Should return true only in cases when valid OS socket exists. - */ - bool isValid() const; - - /** node type */ - virtual NodeType getType() const { return NodeType::kNode; } - - NodeStatus status() const; - - uint32_t getFTLVersion() const; - uint8_t getFTLMajor() const { return getFTLVersion() >> 16; } - uint8_t getFTLMinor() const { return (getFTLVersion() >> 8) & 0xFF; } - uint8_t getFTLPatch() const { return getFTLVersion() & 0xFF; } - - /** - * Get the sockets protocol, address and port as a url string. This will be - * the same as the initial connection string on the client. - */ - std::string getURI() const; - - /** - * Get the UUID for this peer. - */ - const ftl::UUID &id() const; - - /** - * Get the peer id as a string. - */ - std::string to_string() const; - - void noReconnect(); - - unsigned int localID(); - - int connectionCount() const; - - protected: - ftl::net::PeerPtr peer_; +class Node { + public: + /** Peer for outgoing connection: resolve address and connect */ + explicit Node(const ftl::net::PeerPtr &impl); + virtual ~Node(); + + /** + * Close the peer if open. Setting retry parameter to true will initiate + * backoff retry attempts. This is used to deliberately close a connection + * and not for error conditions where different close semantics apply. + * + * @param retry Should reconnection be attempted? + */ + void close(bool retry = false); + + bool isConnected() const; + /** + * Block until the connection and handshake has completed. You should use + * onConnect callbacks instead of blocking, mostly this is intended for + * the unit tests to keep them synchronous. + * + * @return True if all connections were successful, false if timeout or error. + */ + bool waitConnection(int seconds = 1); + + /** + * Make a reconnect attempt. Called internally by Universe object. + */ + bool reconnect(); + + bool isOutgoing() const; + + /** + * Test if the connection is valid. This returns true in all conditions + * except where the socket has been disconnected permenantly, or was never + * able to connect, perhaps due to an invalid address, or is in middle of a + * reconnect attempt. (Valid states: kConnecting, kConnected) + * + * Should return true only in cases when valid OS socket exists. + */ + bool isValid() const; + + /** node type */ + virtual NodeType getType() const { return NodeType::kNode; } + + NodeStatus status() const; + uint32_t getFTLVersion() const; + uint8_t getFTLMajor() const { return getFTLVersion() >> 16; } + uint8_t getFTLMinor() const { return (getFTLVersion() >> 8) & 0xFF; } + uint8_t getFTLPatch() const { return getFTLVersion() & 0xFF; } + + /** + * Get the sockets protocol, address and port as a url string. This will be + * the same as the initial connection string on the client. + */ + std::string getURI() const; + + /** + * Get the UUID for this peer. + */ + const ftl::UUID &id() const; + + /** + * Get the peer id as a string. + */ + std::string to_string() const; + + void noReconnect(); + + unsigned int localID(); + + int connectionCount() const; + + protected: + ftl::net::PeerPtr peer_; }; -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/packet.hpp b/include/ftl/protocol/packet.hpp index baaa97adacac140355b9de357a6ca6eadbf3a750..d75b79c318669e9078a2d426f914474b5a0eb65d 100644 --- a/include/ftl/protocol/packet.hpp +++ b/include/ftl/protocol/packet.hpp @@ -8,9 +8,9 @@ #include <cstdint> #include <vector> +#include <string> #include <ftl/protocol/codecs.hpp> #include <ftl/protocol/channels.hpp> -//#include <msgpack.hpp> namespace ftl { namespace protocol { @@ -23,15 +23,15 @@ static constexpr uint8_t kCurrentFTLVersion = 5; * First bytes of our file format. */ struct Header { - const char magic[4] = {'F','T','L','F'}; - uint8_t version = kCurrentFTLVersion; + const char magic[4] = {'F', 'T', 'L', 'F'}; + uint8_t version = kCurrentFTLVersion; }; /** * Version 2 header padding for potential indexing use. */ struct IndexHeader { - int64_t reserved[8]; + int64_t reserved[8]; }; /** @@ -41,19 +41,17 @@ struct IndexHeader { * an empty wrapper around that. It is used in the encoding callback. */ struct Packet { - ftl::protocol::Codec codec; - uint8_t reserved=0; - uint8_t frame_count=1; // v4+ Frames included in this packet + ftl::protocol::Codec codec = ftl::protocol::Codec::kInvalid; + uint8_t reserved = 0; + uint8_t frame_count = 1; // v4+ Frames included in this packet - uint8_t bitrate=0; // v4+ For multi-bitrate encoding, 0=highest + uint8_t bitrate = 0; // v4+ For multi-bitrate encoding, 0=highest - union { - uint8_t flags=0; // Codec dependent flags (eg. I-Frame or P-Frame) - uint8_t packet_count; - }; - std::vector<uint8_t> data; - - //MSGPACK_DEFINE(codec, reserved, frame_count, bitrate, flags, data); + union { + uint8_t flags = 0; // Codec dependent flags (eg. I-Frame or P-Frame) + uint8_t packet_count; + }; + std::vector<uint8_t> data; }; static constexpr unsigned int kStreamCap_Static = 0x01; @@ -62,23 +60,21 @@ static constexpr unsigned int kStreamCap_NewConnection = 0x04; /** V4 packets have no stream flags field */ struct StreamPacketV4 { - int version=4; // FTL version, Not encoded into stream - - int64_t timestamp; - uint8_t streamID; // Source number [or v4 frameset id] - uint8_t frame_number; // v4+ First frame number (packet may include multiple frames) - ftl::protocol::Channel channel; // Actual channel of this current set of packets + int version = 4; // FTL version, Not encoded into stream - inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; } - inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; } + int64_t timestamp; + uint8_t streamID; // Source number [or v4 frameset id] + uint8_t frame_number; // v4+ First frame number (packet may include multiple frames) + ftl::protocol::Channel channel; // Actual channel of this current set of packets - int64_t localTimestamp; // Not message packet / saved - unsigned int hint_capability; // Is this a video stream, for example - size_t hint_source_total; // Number of tracks per frame to expect + inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; } + inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; } - //MSGPACK_DEFINE(timestamp, streamID, frame_number, channel); + int64_t localTimestamp; // Not message packet / saved + unsigned int hint_capability; // Is this a video stream, for example + size_t hint_source_total; // Number of tracks per frame to expect - operator std::string() const; + operator std::string() const; }; /** @@ -87,26 +83,24 @@ struct StreamPacketV4 { * or included before a frame packet structure. */ struct StreamPacket { - int version = kCurrentFTLVersion; // FTL version, Not encoded into stream - - int64_t timestamp; - uint8_t streamID; // Source number [or v4 frameset id] - uint8_t frame_number; // v4+ First frame number (packet may include multiple frames) - ftl::protocol::Channel channel; // Actual channel of this current set of packets - uint8_t flags=0; + int version = kCurrentFTLVersion; // FTL version, Not encoded into stream - inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; } - inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; } + int64_t timestamp; + uint8_t streamID; // Source number [or v4 frameset id] + uint8_t frame_number; // v4+ First frame number (packet may include multiple frames) + ftl::protocol::Channel channel; // Actual channel of this current set of packets + uint8_t flags = 0; - int64_t localTimestamp; // Not message packet / saved - unsigned int hint_capability; // Is this a video stream, for example - size_t hint_source_total; // Number of tracks per frame to expect - int retry_count = 0; // Decode retry count - unsigned int hint_peerid=0; + inline int frameNumber() const { return (version >= 4) ? frame_number : streamID; } + inline size_t frameSetID() const { return (version >= 4) ? streamID : 0; } - //MSGPACK_DEFINE(timestamp, streamID, frame_number, channel, flags); + int64_t localTimestamp; // Not message packet / saved + unsigned int hint_capability; // Is this a video stream, for example + size_t hint_source_total; // Number of tracks per frame to expect + int retry_count = 0; // Decode retry count + unsigned int hint_peerid = 0; - operator std::string() const; + operator std::string() const; }; /** @@ -114,9 +108,9 @@ struct StreamPacket { * saved or transmitted in a stream together. */ struct PacketPair { - PacketPair(const StreamPacket &s, const Packet &p) : spkt(s), pkt(p) {} - const StreamPacket &spkt; - const Packet &pkt; + PacketPair(const StreamPacket &s, const Packet &p) : spkt(s), pkt(p) {} + const StreamPacket &spkt; + const Packet &pkt; }; } // namespace protocol diff --git a/include/ftl/protocol/self.hpp b/include/ftl/protocol/self.hpp index e21b5645b98a8e1711b8c1f5cc70783d8337cabd..765ccded80dfa57d8339f19a3a517613172c2f7a 100644 --- a/include/ftl/protocol/self.hpp +++ b/include/ftl/protocol/self.hpp @@ -1,19 +1,20 @@ /** - * @file node.hpp + * @file self.hpp * @copyright Copyright (c) 2022 University of Turku, MIT License * @author Nicolas Pope */ #pragma once +#include <memory> +#include <string> +#include <vector> +#include <list> #include <ftl/uuid.hpp> #include <ftl/uri.hpp> #include <ftl/handle.hpp> #include <ftl/protocol/error.hpp> -#include <memory> -#include <string> - namespace ftl { namespace net { class Universe; @@ -24,63 +25,115 @@ namespace protocol { class Node; class Stream; -class Self { - public: - /** Peer for outgoing connection: resolve address and connect */ - explicit Self(const std::shared_ptr<ftl::net::Universe> &impl); - virtual ~Self(); - - std::shared_ptr<ftl::protocol::Node> connectNode(const std::string &uri); - - std::shared_ptr<ftl::protocol::Stream> createStream(const std::string &uri); - - std::shared_ptr<ftl::protocol::Stream> getStream(const std::string &uri); - - void start(); - - /** - * Open a new listening port on a given interfaces. - * eg. "tcp://localhost:9000" - * @param addr URI giving protocol, interface and port - */ - bool listen(const ftl::URI &addr); - - std::vector<ftl::URI> getListeningURIs(); - - /** - * Essential to call this before destroying anything that registered - * callbacks or binds for RPC. It will terminate all connections and - * stop any network activity but without deleting the net object. - */ - void shutdown(); - - bool isConnected(const ftl::URI &uri); - bool isConnected(const std::string &s); - - size_t numberOfNodes() const; - - /** - * Will block until all currently registered connnections have completed. - * You should not use this, but rather use onConnect. - */ - int waitConnections(int seconds = 1); - - /** get peer pointer by peer UUID, returns nullptr if not found */ - std::shared_ptr<ftl::protocol::Node> getNode(const ftl::UUID &pid) const; - /** get webservice peer pointer, returns nullptr if not connected to webservice */ - std::shared_ptr<ftl::protocol::Node> getWebService() const; - std::list<std::shared_ptr<ftl::protocol::Node>> getNodes() const; - - ftl::Handle onConnect(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&)>&); - ftl::Handle onDisconnect(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&)>&); - ftl::Handle onError(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&, ftl::protocol::Error, const std::string & )>&); - - // Used for testing - ftl::net::Universe *getUniverse() const { return universe_.get(); } - - protected: - std::shared_ptr<ftl::net::Universe> universe_; +/** + * @brief A wrapper providing RPC API and local node management. Internally the + * Self instance is responsible for handling all network operations. Typically + * there is just a single instance of this class, but more can be created for + * testing purposes. + * + */ +class Self { + public: + /** Peer for outgoing connection: resolve address and connect */ + explicit Self(const std::shared_ptr<ftl::net::Universe> &impl); + virtual ~Self(); + + /** + * @brief Connect to another host from this Self instance. Usually the + * namespace method can be used instead. + * + * @param uri A TCP URI. + * @return std::shared_ptr<ftl::protocol::Node> + */ + std::shared_ptr<ftl::protocol::Node> connectNode(const std::string &uri); + + /** + * @brief Create a new stream. Use the namespace method if possible. + * + * @param uri A file:// or ftl:// URI. + * @return std::shared_ptr<ftl::protocol::Stream> + */ + std::shared_ptr<ftl::protocol::Stream> createStream(const std::string &uri); + + /** + * @brief Open an existing stream. Use the namespace method if possible. + * + * @param uri A file:// or ftl:// URI + * @return std::shared_ptr<ftl::protocol::Stream> + */ + std::shared_ptr<ftl::protocol::Stream> getStream(const std::string &uri); + + void start(); + + /** + * Open a new listening port on a given interfaces. + * eg. "tcp://localhost:9000" + * @param addr URI giving protocol, interface and port + */ + bool listen(const ftl::URI &addr); + + /** + * @brief Get the list of all listening addresses and ports. + * + * @return std::vector<ftl::URI> + */ + std::vector<ftl::URI> getListeningURIs(); + + /** + * Essential to call this before destroying anything that registered + * callbacks or binds for RPC. It will terminate all connections and + * stop any network activity but without deleting the net object. + */ + void shutdown(); + + bool isConnected(const ftl::URI &uri); + bool isConnected(const std::string &s); + + size_t numberOfNodes() const; + + /** + * Will block until all currently registered connnections have completed. + * You should not use this, but rather use onConnect. + */ + int waitConnections(int seconds = 1); + + /** get peer pointer by peer UUID, returns nullptr if not found */ + std::shared_ptr<ftl::protocol::Node> getNode(const ftl::UUID &pid) const; + /** get webservice peer pointer, returns nullptr if not connected to webservice */ + std::shared_ptr<ftl::protocol::Node> getWebService() const; + std::list<std::shared_ptr<ftl::protocol::Node>> getNodes() const; + + /** + * @brief Register a callback for new node connections. + * + * @return ftl::Handle + */ + ftl::Handle onConnect(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&)>&); + + /** + * @brief Register a callback for node disconnects. + * + * @return ftl::Handle + */ + ftl::Handle onDisconnect(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&)>&); + + /** + * @brief Register a callback for any node or network errors. Note that the node pointer can + * be null if the error was not associated with a specific node. + * + * @return ftl::Handle + */ + ftl::Handle onError( + const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&, + ftl::protocol::Error, + const std::string &)>&); + + // Used for testing + ftl::net::Universe *getUniverse() const { return universe_.get(); } + + protected: + std::shared_ptr<ftl::net::Universe> universe_; }; -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/protocol/streams.hpp b/include/ftl/protocol/streams.hpp index 1ca85e88da5d11fb1682813b83474823e602de9e..2e298debb28015f06f0934cbf12b23cd9d6019bc 100644 --- a/include/ftl/protocol/streams.hpp +++ b/include/ftl/protocol/streams.hpp @@ -6,6 +6,12 @@ #pragma once +#include <string> +#include <vector> +#include <unordered_set> +#include <any> +#include <unordered_map> +#include <memory> #include <ftl/handle.hpp> #include <ftl/threads.hpp> #include <ftl/protocol/channels.hpp> @@ -13,51 +19,52 @@ #include <ftl/protocol/packet.hpp> #include <ftl/protocol/frameid.hpp> #include <ftl/protocol/error.hpp> -#include <string> -#include <vector> -#include <unordered_set> -#include <any> namespace ftl { namespace protocol { -/* Represents a request for data through a stream */ +/** Represents a request for data through a stream */ struct Request { - FrameID id; - ftl::protocol::Channel channel; - int bitrate; - int count; - ftl::protocol::Codec codec; + FrameID id; + ftl::protocol::Channel channel; + int bitrate; + int count; + ftl::protocol::Codec codec; }; using RequestCallback = std::function<bool(const ftl::protocol::Request&)>; using StreamCallback = std::function<bool(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &)>; +/** + * @brief Enumeration of possible stream properties. Not all properties are supported + * by all stream types, but they allow additional control and data access. + * + */ enum struct StreamProperty { kInvalid = 0, kLooping, kSpeed, kBitrate, - kMaxBitrate, + kMaxBitrate, kAdaptiveBitrate, - kObservers, - kURI, - kPaused, - kBytesSent, - kBytesReceived, - kLatency, - kFrameRate, - kName, - kDescription, - kTags, - kUser + kObservers, + kURI, + kPaused, + kBytesSent, + kBytesReceived, + kLatency, + kFrameRate, + kName, + kDescription, + kTags, + kUser }; enum struct StreamType { - kMixed, + kMixed, // Multiple types of stream kUnknown, - kLive, - kRecorded + kLive, // Net stream + kRecorded // File stream }; /** @@ -68,158 +75,271 @@ enum struct StreamType { * Streams are bidirectional, frames can be both received and written. */ class Stream { - public: - virtual ~Stream() {}; - - virtual std::string name() const; - - /** - * Obtain all packets for next frame. The provided callback function is - * called once for every packet. This function might continue to call the - * callback even after the read function returns, for example with a - * NetStream. - */ - ftl::Handle onPacket(const StreamCallback &cb) { return cb_.on(cb); } - - ftl::Handle onRequest(const std::function<bool(const Request &)> &cb) { return request_cb_.on(cb); } - - virtual bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &)=0; - - // TODO: Add methods for: pause, paused, statistics - - /** - * Start the stream. Calls to the onPacket callback will only occur after - * a call to this function (and before a call to end()). - */ - virtual bool begin()=0; - - virtual bool end()=0; - - /** - * Is the stream active? Generally true if begin() has been called, false - * initially and after end(). However, it may go false for other reasons. - * If false, no calls to onPacket will occur and posts will be ignored. - */ - virtual bool active()=0; - - /** - * @brief Clear all state. This will remove all information about available - * and enabled frames or channels. You will then need to enable frames and - * channels again. If active the stream will remain active. - * - */ - virtual void reset(); - - /** - * @brief Re-request all channels and state. This will also cause video encoding - * to generate new I-frames as if a new connection is made. All persistent data - * channels would also become available. For file streams this would reset the - * stream to the beginning of the file. - * - */ - virtual void refresh(); - - /** - * @brief Check if a frame is available. - * - * @param id Frameset and frame number - * @return true if data is available for the frame - * @return false if no data has been seen - */ - bool available(FrameID id) const; - - bool available(FrameID id, ftl::protocol::Channel channel) const; - - bool available(FrameID id, const ftl::protocol::ChannelSet channels) const; - - ftl::Handle onAvailable(const std::function<bool(FrameID, ftl::protocol::Channel)> &cb) { return avail_cb_.on(cb); } - - /** - * @brief Get all channels seen for a frame. If the frame does not exist then - * an empty set is returned. - * - * @param id Frameset and frame number - * @return Set of all seen channels, or empty. - */ - ftl::protocol::ChannelSet channels(FrameID id) const; - - ftl::protocol::ChannelSet enabledChannels(FrameID id) const; - - /** - * @brief Get all available frames in the stream. - * - * @return Set of frame IDs - */ - std::unordered_set<FrameID> frames() const; - - /** - * @brief Get all enabled frames in the stream. - * - * @return Set of frame IDs - */ - std::unordered_set<FrameID> enabled() const; - - /** - * @brief Check if a frame is enabled. - * - * @param id Frameset and frame number - * @return true if data for this frame is enabled. - * @return false if data not enabled or frame does not exist. - */ - bool enabled(FrameID id) const; - - bool enabled(FrameID id, ftl::protocol::Channel channel) const; - - /** - * Number of framesets in stream. - */ - inline size_t size() const { return state_.size(); } - - virtual bool enable(FrameID id); - - virtual bool enable(FrameID id, ftl::protocol::Channel channel); - - virtual bool enable(FrameID id, const ftl::protocol::ChannelSet &channels); - - // TODO: Disable - - virtual void setProperty(ftl::protocol::StreamProperty opt, std::any value)=0; - - virtual std::any getProperty(ftl::protocol::StreamProperty opt)=0; - - virtual bool supportsProperty(ftl::protocol::StreamProperty opt)=0; - - virtual StreamType type() const { return StreamType::kUnknown; } - - ftl::Handle onError(const std::function<bool(ftl::protocol::Error, const std::string &)> &cb) { return error_cb_.on(cb); } - - protected: - void trigger(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt); - - void seen(FrameID id, ftl::protocol::Channel channel); - - void request(const Request &req); - - void error(ftl::protocol::Error, const std::string &str); - - mutable SHARED_MUTEX mtx_; - - private: - struct FSState { - bool enabled = false; - ftl::protocol::ChannelSet selected; - ftl::protocol::ChannelSet available; - // TODO: Add a name and metadata - }; - - ftl::Handler<const ftl::protocol::StreamPacket&, const ftl::protocol::Packet&> cb_; - ftl::Handler<const Request &> request_cb_; - ftl::Handler<FrameID, ftl::protocol::Channel> avail_cb_; - ftl::Handler<ftl::protocol::Error, const std::string&> error_cb_; - std::unordered_map<int, FSState> state_; + public: + virtual ~Stream() {} + + virtual std::string name() const; + + /** + * Obtain all packets for next frame. The provided callback function is + * called once for every packet. This function might continue to call the + * callback even after the read function returns, for example with a + * NetStream. + */ + ftl::Handle onPacket(const StreamCallback &cb) { return cb_.on(cb); } + + /** + * @brief Register a callback for frame and channel requests. Remote machines can send + * requests, at which point the data should be generated and sent properly. + * + * @param cb + * @return ftl::Handle + */ + ftl::Handle onRequest(const std::function<bool(const Request &)> &cb) { return request_cb_.on(cb); } + + /** + * @brief Send packets, either to file or over the network. Packets should follow + * the overall protocol rules, detailed elsewhere. + * + * @return true if sent + * @return false if dropped + */ + virtual bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) = 0; + + // TODO(Nick): Add methods for: pause, paused, statistics + + /** + * Start the stream. Calls to the onPacket callback will only occur after + * a call to this function (and before a call to end()). + */ + virtual bool begin() = 0; + + virtual bool end() = 0; + + /** + * Is the stream active? Generally true if begin() has been called, false + * initially and after end(). However, it may go false for other reasons. + * If false, no calls to onPacket will occur and posts will be ignored. + */ + virtual bool active() = 0; + + /** + * @brief Clear all state. This will remove all information about available + * and enabled frames or channels. You will then need to enable frames and + * channels again. If active the stream will remain active. + * + */ + virtual void reset(); + + /** + * @brief Re-request all channels and state. This will also cause video encoding + * to generate new I-frames as if a new connection is made. All persistent data + * channels would also become available. For file streams this would reset the + * stream to the beginning of the file. + * + */ + virtual void refresh(); + + /** + * @brief Check if a frame is available. + * + * @param id Frameset and frame number + * @return true if data is available for the frame + * @return false if no data has been seen + */ + bool available(FrameID id) const; + + /** + * @brief Check if a channel for a frame is available. + * + * @param id Frameset and frame number + * @param channel + * @return true if there is channel data + * @return false if the channel does not exist + */ + bool available(FrameID id, ftl::protocol::Channel channel) const; + + /** + * @brief Check if all channels in a set are available. + * + * @param id Frameset and frame number + * @param channels Set of channels to check + * @return true if all channels exist + * @return false if one or more do not exist + */ + bool available(FrameID id, const ftl::protocol::ChannelSet channels) const; + + /** + * @brief Register a callback for when new frames and channels become available. + * + * @param cb + * @return ftl::Handle + */ + ftl::Handle onAvailable(const std::function<bool(FrameID, ftl::protocol::Channel)> &cb) { return avail_cb_.on(cb); } + + /** + * @brief Get all channels seen for a frame. If the frame does not exist then + * an empty set is returned. + * + * @param id Frameset and frame number + * @return Set of all seen channels, or empty. + */ + ftl::protocol::ChannelSet channels(FrameID id) const; + + /** + * @brief Obtain a set of all active channels for a frame. + * + * @param id Frameset and frame number + * @return ftl::protocol::ChannelSet + */ + ftl::protocol::ChannelSet enabledChannels(FrameID id) const; + + /** + * @brief Get all available frames in the stream. + * + * @return Set of frame IDs + */ + std::unordered_set<FrameID> frames() const; + + /** + * @brief Get all enabled frames in the stream. + * + * @return Set of frame IDs + */ + std::unordered_set<FrameID> enabled() const; + + /** + * @brief Check if a frame is enabled. + * + * @param id Frameset and frame number + * @return true if data for this frame is enabled. + * @return false if data not enabled or frame does not exist. + */ + bool enabled(FrameID id) const; + + /** + * @brief Check if a specific channel is enabled for a frame. + * + * @param id Frameset and frame number + * @param channel + * @return true if the channel is active + * @return false if the channel is not active or does not exist + */ + bool enabled(FrameID id, ftl::protocol::Channel channel) const; + + /** + * Number of framesets in stream. + */ + inline size_t size() const { return state_.size(); } + + /** + * @brief Activate a frame. This allows availability information to be gathered + * for the frame which might not otherwise be available. However, data is likely + * missing unless a channel is enabled. + * + * @param id Frameset and frame number + * @return true if the frame could be enabled + * @return false if the frame could not be found or enabled + */ + virtual bool enable(FrameID id); + + /** + * @brief Request a specific channel in a frame. Once the request is made data + * should become available if it exists. This will also enable the frame if + * not already enabled. + * + * @param id Frameset and frame number + * @param channel + * @return true if the channel is available and enabled + * @return false if the channel does not exist + */ + virtual bool enable(FrameID id, ftl::protocol::Channel channel); + + /** + * @brief Activate a set of channels for a frame. Requests for data for each + * given channel are sent and the data should then become available. + * + * @param id Frameset and frame number + * @param channels a set of all channels to activate + * @return true if all channels could be enabled + * @return false if some channel could not be enabled + */ + virtual bool enable(FrameID id, const ftl::protocol::ChannelSet &channels); + + // TODO(Nick): Disable + + /** + * @brief Set a stream property to a new value. If the property is not supported, + * is readonly or an invalid value type is given, then an exception is thrown. + * Check if the property is supported first. + * + * @param opt + * @param value + */ + virtual void setProperty(ftl::protocol::StreamProperty opt, std::any value) = 0; + + /** + * @brief Get the value of a stream property. If the property is not supported then + * an exception is thrown. The result is an `any` object that should be casted + * correctly by the user. + * + * @param opt + * @return std::any + */ + virtual std::any getProperty(ftl::protocol::StreamProperty opt) = 0; + + /** + * @brief Check if a property is supported. No exceptions are thrown. + * + * @param opt + * @return true if the property is at least readable + * @return false if the property is unsupported + */ + virtual bool supportsProperty(ftl::protocol::StreamProperty opt) = 0; + + virtual StreamType type() const { return StreamType::kUnknown; } + + /** + * @brief Register a callback for asynchronous stream errors. + * + * @param cb + * @return ftl::Handle + */ + ftl::Handle onError(const std::function<bool(ftl::protocol::Error, const std::string &)> &cb) { + return error_cb_.on(cb); + } + + protected: + /** Dispatch packets to callbacks */ + void trigger(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt); + + /** Mark the channel and frame as available */ + void seen(FrameID id, ftl::protocol::Channel channel); + + /** Dispatch a request */ + void request(const Request &req); + + /** Report a stream error */ + void error(ftl::protocol::Error, const std::string &str); + + mutable SHARED_MUTEX mtx_; + + private: + struct FSState { + bool enabled = false; + ftl::protocol::ChannelSet selected; + ftl::protocol::ChannelSet available; + // TODO(Nick): Add a name and metadata + }; + + ftl::Handler<const ftl::protocol::StreamPacket&, const ftl::protocol::Packet&> cb_; + ftl::Handler<const Request &> request_cb_; + ftl::Handler<FrameID, ftl::protocol::Channel> avail_cb_; + ftl::Handler<ftl::protocol::Error, const std::string&> error_cb_; + std::unordered_map<int, FSState> state_; }; using StreamPtr = std::shared_ptr<Stream>; -} -} +} // namespace protocol +} // namespace ftl diff --git a/include/ftl/threads.hpp b/include/ftl/threads.hpp index 955a54da0d7385642a1dc3de7ab9379692b31439..0d62a76ab0d616dfdb060da269121f64cf032203 100644 --- a/include/ftl/threads.hpp +++ b/include/ftl/threads.hpp @@ -24,47 +24,47 @@ #define RECURSIVE_MUTEX std::recursive_timed_mutex #define SHARED_MUTEX std::shared_timed_mutex -#define UNIQUE_LOCK(M,L) std::unique_lock<std::remove_reference<decltype(M)>::type> L(M, std::chrono::milliseconds(MUTEX_TIMEOUT)); while (!L) { LOG(ERROR) << "Mutex timeout"; L.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT)); }; -#define SHARED_LOCK(M,L) std::shared_lock<std::remove_reference<decltype(M)>::type> L(M, std::chrono::milliseconds(MUTEX_TIMEOUT)); while (!L) { LOG(ERROR) << "Mutex timeout"; L.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT)); }; +#define UNIQUE_LOCK(M, L) std::unique_lock<std::remove_reference<decltype(M)>::type> L(M, std::chrono::milliseconds(MUTEX_TIMEOUT)); while (!L) { LOG(ERROR) << "Mutex timeout"; L.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT)); }; +#define SHARED_LOCK(M, L) std::shared_lock<std::remove_reference<decltype(M)>::type> L(M, std::chrono::milliseconds(MUTEX_TIMEOUT)); while (!L) { LOG(ERROR) << "Mutex timeout"; L.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT)); }; #else #define MUTEX std::mutex #define RECURSIVE_MUTEX std::recursive_mutex #define SHARED_MUTEX std::shared_mutex -#define UNIQUE_LOCK(M,L) std::unique_lock<std::remove_reference<decltype(M)>::type> L(M); -#define SHARED_LOCK(M,L) std::shared_lock<std::remove_reference<decltype(M)>::type> L(M); +#define UNIQUE_LOCK(M, L) std::unique_lock<std::remove_reference<decltype(M)>::type> L(M); +#define SHARED_LOCK(M, L) std::shared_lock<std::remove_reference<decltype(M)>::type> L(M); #endif // DEBUG_MUTEX #define SHARED_LOCK_TYPE(M) std::shared_lock<M> namespace ftl { - extern ctpl::thread_pool pool; + extern ctpl::thread_pool pool; namespace threads { /** Upgrade shared lock to exclusive lock and re-acquire shared lock at end of * scope. */ class _write_lock { -public: - explicit _write_lock(std::shared_mutex& mtx) : mtx_(&mtx) { - mtx_->unlock_shared(); - mtx_->lock(); - } - - ~_write_lock() { - mtx_->unlock(); - mtx_->lock_shared(); - } - -private: - std::shared_mutex* const mtx_; + public: + explicit _write_lock(std::shared_mutex& mtx) : mtx_(&mtx) { + mtx_->unlock_shared(); + mtx_->lock(); + } + + ~_write_lock() { + mtx_->unlock(); + mtx_->lock_shared(); + } + + private: + std::shared_mutex* const mtx_; }; /** Upgrade already acquired SHARED_LOCK to exclusive lock and re-acquire shared * lock at end of scope. Shared lock must be already acquired on mutex! If more * careful locking is required, use std::..._lock directly */ -#define WRITE_LOCK(M,L) ftl::threads::_write_lock L(M); +#define WRITE_LOCK(M, L) ftl::threads::_write_lock L(M); -} -} +} // namespace threads +} // namespace ftl diff --git a/include/ftl/time.hpp b/include/ftl/time.hpp index 338b63c49e5d8cb5f9268c2f69dae226e51ce230..dc8ded925f59e04d6583595463610ae9d0fe4dda 100644 --- a/include/ftl/time.hpp +++ b/include/ftl/time.hpp @@ -1,3 +1,9 @@ +/** + * @file time.hpp + * @copyright Copyright (c) 2020 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once #include <cinttypes> @@ -20,5 +26,5 @@ double get_time_seconds(); */ void setClockAdjustment(int64_t ms); -} -} +} // namespace time +} // namespace ftl diff --git a/include/ftl/uri.hpp b/include/ftl/uri.hpp index 4d8b7e7bc763be957cd8ddc0eb4066180476bfa5..c1b8abe87c1bc441a36e4190bf27f4cbc09fc6ae 100644 --- a/include/ftl/uri.hpp +++ b/include/ftl/uri.hpp @@ -6,109 +6,110 @@ #pragma once -#include <nlohmann/json_fwd.hpp> #include <uriparser/Uri.h> #include <string> #include <vector> #include <map> +#include <nlohmann/json_fwd.hpp> + namespace ftl { - typedef const char * uri_t; - - /** - * Universal Resource Identifier. Parse, modify, represent and generate URIs. - */ - class URI { - public: - URI(): m_valid(false) {} - explicit URI(uri_t puri); - explicit URI(const std::string &puri); - explicit URI(const URI &c); - - ~URI() {}; - - enum scheme_t : int { - SCHEME_NONE, - SCHEME_TCP, - SCHEME_UDP, - SCHEME_FTL, // Future Tech Lab - SCHEME_HTTP, - SCHEME_WS, - SCHEME_WSS, - SCHEME_IPC, - SCHEME_FILE, - SCHEME_OTHER, - SCHEME_DEVICE, - SCHEME_GROUP - }; - - bool isValid() const { return m_valid; }; - const std::string &getHost() const { return m_host; }; - int getPort() const { return m_port; }; - scheme_t getProtocol() const { return m_proto; }; - scheme_t getScheme() const { return m_proto; }; - const std::string &getPath() const { return m_path; }; - const std::string &getFragment() const { return m_frag; } - std::string getQuery() const; - const std::string &getBaseURI() const { return m_base; }; - bool hasUserInfo() const; - const std::string &getUserInfo() const; - - /** - * Get the URI without query parameters, and limit path to length N. - * If N is negative then it is taken from full path length. - */ - std::string getBaseURI(int n) const; - - std::string getBaseURIWithUser() const; - - std::string getPathSegment(int n) const; - - inline size_t getPathLength() const { return m_pathseg.size(); } - - void setAttribute(const std::string &key, const std::string &value); - void setAttribute(const std::string &key, int value); - - template <typename T> - T getAttribute(const std::string &key) const { - auto i = m_qmap.find(key); - return (i != m_qmap.end()) ? T(i->second) : T(); - } - - bool hasAttribute(const std::string &a) const { return m_qmap.count(a) > 0; } - - std::string to_string() const; - - void to_json(nlohmann::json &) const; - - private: - void _parse(uri_t puri); - - private: - bool m_valid; - std::string m_host; - std::string m_path; - std::string m_frag; - std::string m_base; - std::string m_userinfo; - std::vector<std::string> m_pathseg; - int m_port = 0; - scheme_t m_proto = scheme_t::SCHEME_NONE; - std::string m_protostr; - // std::string m_query; - std::map<std::string, std::string> m_qmap; - }; - - template <> - inline int URI::getAttribute<int>(const std::string &key) const { - auto i = m_qmap.find(key); - return (i != m_qmap.end()) ? std::stoi(i->second) : 0; - } - - template <> - inline std::string URI::getAttribute<std::string>(const std::string &key) const { - auto i = m_qmap.find(key); - return (i != m_qmap.end()) ? i->second : ""; - } +typedef const char * uri_t; + +/** + * Universal Resource Identifier. Parse, modify, represent and generate URIs. + */ +class URI { + public: + URI(): m_valid(false) {} + explicit URI(uri_t puri); + explicit URI(const std::string &puri); + explicit URI(const URI &c); + + ~URI() {}; + + enum scheme_t : int { + SCHEME_NONE, + SCHEME_TCP, + SCHEME_UDP, + SCHEME_FTL, // Future Tech Lab + SCHEME_HTTP, + SCHEME_WS, + SCHEME_WSS, + SCHEME_IPC, + SCHEME_FILE, + SCHEME_OTHER, + SCHEME_DEVICE, + SCHEME_GROUP + }; + + bool isValid() const { return m_valid; } + const std::string &getHost() const { return m_host; } + int getPort() const { return m_port; } + scheme_t getProtocol() const { return m_proto; } + scheme_t getScheme() const { return m_proto; } + const std::string &getPath() const { return m_path; } + const std::string &getFragment() const { return m_frag; } + std::string getQuery() const; + const std::string &getBaseURI() const { return m_base; } + bool hasUserInfo() const; + const std::string &getUserInfo() const; + + /** + * Get the URI without query parameters, and limit path to length N. + * If N is negative then it is taken from full path length. + */ + std::string getBaseURI(int n) const; + + std::string getBaseURIWithUser() const; + + std::string getPathSegment(int n) const; + + inline size_t getPathLength() const { return m_pathseg.size(); } + + void setAttribute(const std::string &key, const std::string &value); + void setAttribute(const std::string &key, int value); + + template <typename T> + T getAttribute(const std::string &key) const { + auto i = m_qmap.find(key); + return (i != m_qmap.end()) ? T(i->second) : T(); + } + + bool hasAttribute(const std::string &a) const { return m_qmap.count(a) > 0; } + + std::string to_string() const; + + void to_json(nlohmann::json &) const; + + private: + void _parse(uri_t puri); + + bool m_valid; + std::string m_host; + std::string m_path; + std::string m_frag; + std::string m_base; + std::string m_userinfo; + std::vector<std::string> m_pathseg; + int m_port = 0; + scheme_t m_proto = scheme_t::SCHEME_NONE; + std::string m_protostr; + // std::string m_query; + std::map<std::string, std::string> m_qmap; +}; + +template <> +inline int URI::getAttribute<int>(const std::string &key) const { + auto i = m_qmap.find(key); + return (i != m_qmap.end()) ? std::stoi(i->second) : 0; +} + +template <> +inline std::string URI::getAttribute<std::string>(const std::string &key) const { + auto i = m_qmap.find(key); + return (i != m_qmap.end()) ? i->second : ""; } + +} // namespace ftl diff --git a/include/ftl/utility/base64.hpp b/include/ftl/utility/base64.hpp index 197bd7df333629c5e8316fd36a7e654977ecd30f..cc1c4aea845877aff2e63b590398e0ede4507398 100644 --- a/include/ftl/utility/base64.hpp +++ b/include/ftl/utility/base64.hpp @@ -3,8 +3,7 @@ // Version: 2.rc.04 (release candidate) // -#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A -#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#pragma once #include <string> @@ -12,8 +11,8 @@ #include <string_view> #endif // __cplusplus >= 201703L -std::string base64_encode (std::string const& s, bool url = false); -std::string base64_encode_pem (std::string const& s); +std::string base64_encode(std::string const& s, bool url = false); +std::string base64_encode_pem(std::string const& s); std::string base64_encode_mime(std::string const& s); std::string base64_decode(std::string const& s, bool remove_linebreaks = false); @@ -25,11 +24,9 @@ std::string base64_encode(unsigned char const*, size_t len, bool url = false); // Requires C++17 // Provided by Yannic Bonenberger (https://github.com/Yannic) // -std::string base64_encode (std::string_view s, bool url = false); -std::string base64_encode_pem (std::string_view s); +std::string base64_encode(std::string_view s, bool url = false); +std::string base64_encode_pem(std::string_view s); std::string base64_encode_mime(std::string_view s); std::string base64_decode(std::string_view s, bool remove_linebreaks = false); #endif // __cplusplus >= 201703L - -#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ diff --git a/include/ftl/uuid.hpp b/include/ftl/uuid.hpp index 875a698c98a9e725c397e3f53d1b2d8211c4bcc3..cb6e75c29b1b60587245ebbd1786018ed68f1d91 100644 --- a/include/ftl/uuid.hpp +++ b/include/ftl/uuid.hpp @@ -22,130 +22,131 @@ #include <msgpack.hpp> namespace ftl { - /** - * C++ Wrapper for libuuid. The default constructor generates a new UUID. - */ - class UUID { - public: - UUID() { + /** + * C++ Wrapper for libuuid. The default constructor generates a new UUID. + */ +class UUID { + public: + UUID() { #ifdef WIN32 - ::UuidCreate(&guid_); + ::UuidCreate(&guid_); #else - uuid_generate(uuid_); + uuid_generate(uuid_); #endif - } - explicit UUID(int u) { memset(uuid_,u,16); } - UUID(const ftl::UUID &u) { memcpy(uuid_,u.uuid_,16); } - explicit UUID(const unsigned char *raw) { memcpy(uuid_, raw, 16); } - explicit UUID(const std::string &s) { + } + explicit UUID(int u) { memset(uuid_, u, 16); } + explicit UUID(const ftl::UUID &u) { memcpy(uuid_, u.uuid_, 16); } + explicit UUID(const unsigned char *raw) { memcpy(uuid_, raw, 16); } + explicit UUID(const std::string &s) { #ifdef WIN32 - // TODO(Nick) Windows UUID parse - LOG(ERROR) << "TODO: parse UUID from string (Windows)"; + // TODO(Nick) Windows UUID parse + LOG(ERROR) << "TODO: parse UUID from string (Windows)"; #else - if (uuid_parse(s.c_str(), uuid_) < 0) { - memset(uuid_,0,16); - } + if (uuid_parse(s.c_str(), uuid_) < 0) { + memset(uuid_, 0, 16); + } #endif - } - - UUID &operator=(const UUID &u) { - memcpy(&uuid_,&u.uuid_,16); return *this; - } - bool operator==(const UUID &u) const { - return memcmp(&uuid_,&u.uuid_,16) == 0; - } - bool operator!=(const UUID &u) const { - return memcmp(&uuid_,&u.uuid_,16) != 0; - } - bool operator<(const UUID &u) const { - return strncmp((const char*)uuid_, (const char *)u.uuid_, 16) < 0; - } - - /// returns false if all bytes zero, otherwise returns true - bool is_valid() { - bool all_zeros = true; - for (int i = 0; i < 16; i++) { all_zeros &= (0u == uuid_[i]); } - return !all_zeros; - } + } - /** - * Get a raw data string. - */ - std::string str() const { return std::string((char*)&uuid_,16); } - const unsigned char *raw() const { return (const unsigned char*)&uuid_; } - - /** - * Get a pretty string. - */ - std::string to_string() const { - static const char *digits = "0123456789abcdef"; - std::string rc(sizeof(uuid_)*2+4,'0'); + UUID &operator=(const UUID &u) { + memcpy(&uuid_, &u.uuid_, 16); + return *this; + } + bool operator==(const UUID &u) const { + return memcmp(&uuid_, &u.uuid_, 16) == 0; + } + bool operator!=(const UUID &u) const { + return memcmp(&uuid_, &u.uuid_, 16) != 0; + } + bool operator<(const UUID &u) const { + return strncmp((const char*)uuid_, (const char *)u.uuid_, 16) < 0; + } - size_t j=0; - for (size_t i=0 ; i<4; ++i) { - rc[j+1] = digits[uuid_[i] & 0x0f]; - rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; - j+=2; - } - rc[j++] = '-'; - for (size_t i=4 ; i<6; ++i) { - rc[j+1] = digits[uuid_[i] & 0x0f]; - rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; - j+=2; - } - rc[j++] = '-'; - for (size_t i=6 ; i<8; ++i) { - rc[j+1] = digits[uuid_[i] & 0x0f]; - rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; - j+=2; - } - rc[j++] = '-'; - for (size_t i=8 ; i<10; ++i) { - rc[j+1] = digits[uuid_[i] & 0x0f]; - rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; - j+=2; - } - rc[j++] = '-'; - for (size_t i=10 ; i<16; ++i) { - rc[j+1] = digits[uuid_[i] & 0x0f]; - rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; - j+=2; - } - return rc; + /// returns false if all bytes zero, otherwise returns true + bool is_valid() { + bool all_zeros = true; + for (int i = 0; i < 16; i++) { all_zeros &= (0u == uuid_[i]); } + return !all_zeros; + } + + /** + * Get a raw data string. + */ + std::string str() const { return std::string(reinterpret_cast<const char*>(&uuid_), 16); } + const unsigned char *raw() const { return (const unsigned char*)&uuid_; } + + /** + * Get a pretty string. + */ + std::string to_string() const { + static const char *digits = "0123456789abcdef"; + std::string rc(sizeof(uuid_)*2+4, '0'); + + size_t j = 0; + for (size_t i = 0 ; i < 4; ++i) { + rc[j+1] = digits[uuid_[i] & 0x0f]; + rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; + j+=2; + } + rc[j++] = '-'; + for (size_t i = 4 ; i < 6; ++i) { + rc[j+1] = digits[uuid_[i] & 0x0f]; + rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; + j+=2; + } + rc[j++] = '-'; + for (size_t i = 6 ; i < 8; ++i) { + rc[j+1] = digits[uuid_[i] & 0x0f]; + rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; + j+=2; + } + rc[j++] = '-'; + for (size_t i = 8 ; i < 10; ++i) { + rc[j+1] = digits[uuid_[i] & 0x0f]; + rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; + j+=2; + } + rc[j++] = '-'; + for (size_t i = 10 ; i < 16; ++i) { + rc[j+1] = digits[uuid_[i] & 0x0f]; + rc[j] = digits[(uuid_[i] >> 4) & 0x0f]; + j+=2; + } + return rc; /* #ifdef WIN32 - RPC_CSTR szUuid = NULL; - if (::UuidToStringA(&guid_, &szUuid) == RPC_S_OK) { - return std::string((char*)szUuid); - } - return "00000000-0000-0000-0000-000000000000"; + RPC_CSTR szUuid = NULL; + if (::UuidToStringA(&guid_, &szUuid) == RPC_S_OK) { + return std::string((char*)szUuid); + } + return "00000000-0000-0000-0000-000000000000"; #else - char b[37]; - uuid_unparse(uuid_, b); - return std::string(b); + char b[37]; + uuid_unparse(uuid_, b); + return std::string(b); #endif */ - } - - /* Allow the UUID to be packed into an RPC message. */ - MSGPACK_DEFINE(uuid_); - - private: + } + + /* Allow the UUID to be packed into an RPC message. */ + MSGPACK_DEFINE(uuid_); + + private: #ifdef WIN32 - union { - _GUID guid_; - unsigned char uuid_[16]; - }; + union { + _GUID guid_; + unsigned char uuid_[16]; + }; #else - unsigned char uuid_[16]; + unsigned char uuid_[16]; #endif - }; }; +} // namespace ftl namespace std { - template <> struct hash<ftl::UUID> { - size_t operator()(const ftl::UUID & x) const { - return std::hash<std::string>{}(x.str()); - } - }; +template <> struct hash<ftl::UUID> { + size_t operator()(const ftl::UUID & x) const { + return std::hash<std::string>{}(x.str()); + } }; +} diff --git a/src/base64.cpp b/src/base64.cpp index a1be53b84b72f87ba937cd0f12ec5b0df8ab8c96..2eabe6c710016482a50b4475f8f59325f77d7266 100644 --- a/src/base64.cpp +++ b/src/base64.cpp @@ -1,84 +1,84 @@ /* - base64.cpp and base64.h + base64.cpp and base64.h - base64 encoding and decoding with C++. - More information at - https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp - Version: 2.rc.04 (release candidate) + Version: 2.rc.04 (release candidate) - Copyright (C) 2004-2017, 2020 René Nyffenegger + Copyright (C) 2004-2017, 2020 René Nyffenegger - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. - 3. This notice may not be removed or altered from any source distribution. + 3. This notice may not be removed or altered from any source distribution. - René Nyffenegger rene.nyffenegger@adp-gmbh.ch + René Nyffenegger rene.nyffenegger@adp-gmbh.ch */ #include <ftl/utility/base64.hpp> - // - // Depending on the url parameter in base64_chars, one of - // two sets of base64 characters needs to be chosen. - // They differ in their last two characters. - // +// +// Depending on the url parameter in base64_chars, one of +// two sets of base64 characters needs to be chosen. +// They differ in their last two characters. +// const char* base64_chars[2] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "+/", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "-_"}; + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; static unsigned int pos_of_char(const unsigned char chr) { - // - // Return the position of chr within base64_encode() - // +// +// Return the position of chr within base64_encode() +// - if (chr >= 'A' && chr <= 'Z') return chr - 'A'; - else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; - else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; - else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( - else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' - throw "If input is correct, this line should never be reached."; + throw "If input is correct, this line should never be reached."; } static std::string insert_linebreaks(std::string str, size_t distance) { - // - // Provided by https://github.com/JomaCorpFX, adapted by me. - // - if (!str.length()) { - return ""; - } +// +// Provided by https://github.com/JomaCorpFX, adapted by me. +// + if (!str.length()) { + return ""; + } - size_t pos = distance; + size_t pos = distance; - while (pos < str.size()) { - str.insert(pos, "\n"); - pos += distance + 1; - } + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } - return str; + return str; } template <typename String, unsigned int line_length> @@ -103,131 +103,131 @@ static std::string encode(String s, bool url) { std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { - size_t len_encoded = (in_len +2) / 3 * 4; + size_t len_encoded = (in_len +2) / 3 * 4; - unsigned char trailing_char = url ? '.' : '='; + unsigned char trailing_char = url ? '.' : '='; - // - // Choose set of base64 characters. They differ - // for the last two positions, depending on the url - // parameter. - // A bool (as is the parameter url) is guaranteed - // to evaluate to either 0 or 1 in C++ therfore, - // the correct character set is chosen by subscripting - // base64_chars with url. - // - const char* base64_chars_ = base64_chars[url]; +// +// Choose set of base64 characters. They differ +// for the last two positions, depending on the url +// parameter. +// A bool (as is the parameter url) is guaranteed +// to evaluate to either 0 or 1 in C++ therfore, +// the correct character set is chosen by subscripting +// base64_chars with url. +// + const char* base64_chars_ = base64_chars[url]; - std::string ret; - ret.reserve(len_encoded); + std::string ret; + ret.reserve(len_encoded); - unsigned int pos = 0; + unsigned int pos = 0; - while (pos < in_len) { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); - if (pos+1 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + if (pos+1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); - if (pos+2 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); - ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); - } - else { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); - ret.push_back(trailing_char); - } - } - else { + if (pos+2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); - ret.push_back(trailing_char); - ret.push_back(trailing_char); - } + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } - pos += 3; - } + pos += 3; + } - return ret; + return ret; } template <typename String> static std::string decode(String encoded_string, bool remove_linebreaks) { - // - // decode(…) is templated so that it can be used with String = const std::string& - // or std::string_view (requires at least C++17) - // +// +// decode(…) is templated so that it can be used with String = const std::string& +// or std::string_view (requires at least C++17) +// - if (remove_linebreaks) { + if (remove_linebreaks) { - if (! encoded_string.length() ) { - return ""; - } + if (! encoded_string.length() ) { + return ""; + } - std::string copy(encoded_string); + std::string copy(encoded_string); - size_t pos=0; - while ((pos = copy.find("\n", pos)) != std::string::npos) { - copy.erase(pos, 1); - } + size_t pos=0; + while ((pos = copy.find("\n", pos)) != std::string::npos) { + copy.erase(pos, 1); + } - return base64_decode(copy, false); + return base64_decode(copy, false); - } + } - size_t length_of_string = encoded_string.length(); - if (!length_of_string) return std::string(""); + size_t length_of_string = encoded_string.length(); + if (!length_of_string) return std::string(""); - size_t in_len = length_of_string; - size_t pos = 0; + size_t in_len = length_of_string; + size_t pos = 0; - // - // The approximate length (bytes) of the decoded string might be one ore - // two bytes smaller, depending on the amount of trailing equal signs - // in the encoded string. This approximation is needed to reserve - // enough space in the string to be returned. - // - size_t approx_length_of_decoded_string = length_of_string / 4 * 3; - std::string ret; - ret.reserve(approx_length_of_decoded_string); +// +// The approximate length (bytes) of the decoded string might be one ore +// two bytes smaller, depending on the amount of trailing equal signs +// in the encoded string. This approximation is needed to reserve +// enough space in the string to be returned. +// + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); - while (pos < in_len) { + while (pos < in_len) { - unsigned int pos_of_char_1 = pos_of_char(encoded_string[pos+1] ); + unsigned int pos_of_char_1 = pos_of_char(encoded_string[pos+1] ); - ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char(encoded_string[pos+0]) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); + ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char(encoded_string[pos+0]) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); - if (encoded_string[pos+2] != '=' && encoded_string[pos+2] != '.') { // accept URL-safe base 64 strings, too, so check for '.' also. + if (encoded_string[pos+2] != '=' && encoded_string[pos+2] != '.') { // accept URL-safe base 64 strings, too, so check for '.' also. - unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos+2] ); - ret.push_back(static_cast<std::string::value_type>( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); + unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos+2] ); + ret.push_back(static_cast<std::string::value_type>( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); - if (encoded_string[pos+3] != '=' && encoded_string[pos+3] != '.') { - ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string[pos+3]) )); - } - } + if (encoded_string[pos+3] != '=' && encoded_string[pos+3] != '.') { + ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string[pos+3]) )); + } + } - pos += 4; - } + pos += 4; + } - return ret; + return ret; } std::string base64_decode(std::string const& s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); + return decode(s, remove_linebreaks); } std::string base64_encode(std::string const& s, bool url) { - return encode(s, url); + return encode(s, url); } std::string base64_encode_pem (std::string const& s) { - return encode_pem(s); + return encode_pem(s); } std::string base64_encode_mime(std::string const& s) { - return encode_mime(s); + return encode_mime(s); } #if __cplusplus >= 201703L @@ -238,19 +238,19 @@ std::string base64_encode_mime(std::string const& s) { // std::string base64_encode(std::string_view s, bool url) { - return encode(s, url); + return encode(s, url); } std::string base64_encode_pem(std::string_view s) { - return encode_pem(s); + return encode_pem(s); } std::string base64_encode_mime(std::string_view s) { - return encode_mime(s); + return encode_mime(s); } std::string base64_decode(std::string_view s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); + return decode(s, remove_linebreaks); } #endif // __cplusplus >= 201703L diff --git a/src/channelSet.cpp b/src/channelSet.cpp index 1340df450ad4af316259804bd44d81e33e66c9ee..c0c1702a83b02aeb0e93df3124bac404fa6b6a74 100644 --- a/src/channelSet.cpp +++ b/src/channelSet.cpp @@ -1,27 +1,33 @@ +/** + * @file channelSet.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol/channelSet.hpp> using ftl::protocol::ChannelSet; ChannelSet operator&(const ChannelSet &a, const ChannelSet &b) { - ChannelSet result; - for (auto &i : a) { - if (b.find(i) != b.end()) result.insert(i); - } - return result; + ChannelSet result; + for (auto &i : a) { + if (b.find(i) != b.end()) result.insert(i); + } + return result; } ChannelSet operator-(const ChannelSet &a, const ChannelSet &b) { - ChannelSet result; - for (auto &i : a) { - if (b.find(i) == b.end()) result.insert(i); - } - return result; + ChannelSet result; + for (auto &i : a) { + if (b.find(i) == b.end()) result.insert(i); + } + return result; } bool operator!=(const ChannelSet &a, const ChannelSet &b) { - if (a.size() != b.size()) return true; - for (auto &i : a) { - if (b.count(i) == 0) return true; - } - return false; + if (a.size() != b.size()) return true; + for (auto &i : a) { + if (b.count(i) == 0) return true; + } + return false; } diff --git a/src/common.hpp b/src/common.hpp index b64dab520d8eb1ab0ccb55cec2fabcf2da6af50e..a426778ad4a8037e103b09eba8ca1c6a7504f366 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -6,7 +6,7 @@ #pragma once -// TODO: remove platform specific headers from here +// TODO(Nick): remove platform specific headers from here #ifndef WIN32 #include <unistd.h> #include <sys/poll.h> diff --git a/src/ctpl_stl.cpp b/src/ctpl_stl.cpp index 2130650eaa34b8a28735290725c6fa4a9513a258..947560af1898aeec977e707f367959e75aebf701 100644 --- a/src/ctpl_stl.cpp +++ b/src/ctpl_stl.cpp @@ -6,14 +6,14 @@ #include <ftl/lib/ctpl_stl.hpp> void ctpl::thread_pool::set_thread(int i) { - std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag + std::shared_ptr<std::atomic<bool>> flag(this->flags[i]); // a copy of the shared ptr to the flag auto f = [this, i, flag/* a copy of the shared ptr to the flag */]() { std::atomic<bool> & _flag = *flag; std::function<void(int id)> * _f; bool isPop = this->q.pop(_f); while (true) { while (isPop) { // if there is anything in the queue - std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred + std::unique_ptr<std::function<void(int id)>> func(_f); // at return, delete the function even if an exception occurred (*_f)(i); if (_flag) return; // the thread is wanted to stop, return even if the queue is not empty yet @@ -29,7 +29,7 @@ void ctpl::thread_pool::set_thread(int i) { return; // if the queue is empty and this->isDone == true or *flag then return } }; - this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() + this->threads[i].reset(new std::thread(f)); // compiler may not support std::make_unique() // For excess threads, ensure they only operate if needed. /*if (i >= std::thread::hardware_concurrency()-1) { diff --git a/src/dispatcher.cpp b/src/dispatcher.cpp index bd4f721d8ff13f6d630bb5b030ce191083abe29c..57c9475f516496bdcab197e0f3936fb5bf4a2a9f 100644 --- a/src/dispatcher.cpp +++ b/src/dispatcher.cpp @@ -4,11 +4,11 @@ * @author Nicolas Pope */ +#include <iostream> #include <ftl/lib/loguru.hpp> #include "dispatcher.hpp" #include "peer.hpp" #include <ftl/exception.hpp> -#include <iostream> using ftl::net::Peer; using ftl::net::Dispatcher; @@ -17,145 +17,142 @@ using std::string; using std::optional; std::string object_type_to_string(const msgpack::type::object_type t) { - switch(t) { - case msgpack::type::object_type::NIL: return "NIL"; - case msgpack::type::object_type::BOOLEAN: return "BOOLEAN"; - case msgpack::type::object_type::POSITIVE_INTEGER: return "POSITIVE_INTEGER"; - case msgpack::type::object_type::NEGATIVE_INTEGER: return "NEGATIVE_INTEGER"; - case msgpack::type::object_type::FLOAT32: return "FLOAT32"; - case msgpack::type::object_type::FLOAT64: return "FLOAT64"; - //case msgpack::type::object_type::FLOAT: return "FLOAT"; - case msgpack::type::object_type::STR: return "STR"; - case msgpack::type::object_type::BIN: return "BIN"; - case msgpack::type::object_type::ARRAY: return "ARRAY"; - case msgpack::type::object_type::MAP: return "MAP"; - case msgpack::type::object_type::EXT: return "EXT"; - } - return "UNKNOWN"; + switch (t) { + case msgpack::type::object_type::NIL: return "NIL"; + case msgpack::type::object_type::BOOLEAN: return "BOOLEAN"; + case msgpack::type::object_type::POSITIVE_INTEGER: return "POSITIVE_INTEGER"; + case msgpack::type::object_type::NEGATIVE_INTEGER: return "NEGATIVE_INTEGER"; + case msgpack::type::object_type::FLOAT32: return "FLOAT32"; + case msgpack::type::object_type::FLOAT64: return "FLOAT64"; + case msgpack::type::object_type::STR: return "STR"; + case msgpack::type::object_type::BIN: return "BIN"; + case msgpack::type::object_type::ARRAY: return "ARRAY"; + case msgpack::type::object_type::MAP: return "MAP"; + case msgpack::type::object_type::EXT: return "EXT"; + } + return "UNKNOWN"; } vector<string> Dispatcher::getBindings() const { - vector<string> res; - for (auto x : funcs_) { - res.push_back(x.first); - } - return res; + vector<string> res; + for (auto x : funcs_) { + res.push_back(x.first); + } + return res; } void ftl::net::Dispatcher::dispatch(Peer &s, const msgpack::object &msg) { - switch (msg.via.array.size) { - case 3: - dispatch_notification(s, msg); break; - case 4: - dispatch_call(s, msg); break; - default: - throw FTL_Error("Unrecognised msgpack : " << msg.via.array.size); - } + switch (msg.via.array.size) { + case 3: + dispatch_notification(s, msg); + break; + case 4: + dispatch_call(s, msg); + break; + default: + throw FTL_Error("Unrecognised msgpack : " << msg.via.array.size); + } } void ftl::net::Dispatcher::dispatch_call(Peer &s, const msgpack::object &msg) { - call_t the_call; - - try { - msg.convert(the_call); - } catch(...) { - throw FTL_Error("Bad message format"); - } - - // TODO: proper validation of protocol (and responding to it) - auto &&type = std::get<0>(the_call); - auto &&id = std::get<1>(the_call); - auto &&name = std::get<2>(the_call); - auto &&args = std::get<3>(the_call); - // assert(type == 0); - - if (type == 1) { - s._dispatchResponse(id, name, args); - } else if (type == 0) { - DLOG(2) << "RPC " << name << "() <- " << s.getURI(); - - auto func = _locateHandler(name); - - if (func) { - //DLOG(INFO) << "Found binding for " << name; - try { - auto result = (*func)(s, args); //->get(); - s._sendResponse(id, name, result->get()); - } catch (const std::exception &e) { - throw FTL_Error("Exception when attempting to call RPC " << name << " (" << e.what() << ")"); - // FIXME: Send the error in the response. - } - } else { - throw FTL_Error("No binding found for " << name); - } - } else { - throw FTL_Error("Unrecognised message type: " << type); - } + call_t the_call; + + try { + msg.convert(the_call); + } catch(...) { + throw FTL_Error("Bad message format"); + } + + // TODO(Nick): proper validation of protocol (and responding to it) + auto &&type = std::get<0>(the_call); + auto &&id = std::get<1>(the_call); + auto &&name = std::get<2>(the_call); + auto &&args = std::get<3>(the_call); + // assert(type == 0); + + if (type == 1) { + s._dispatchResponse(id, name, args); + } else if (type == 0) { + DLOG(2) << "RPC " << name << "() <- " << s.getURI(); + + auto func = _locateHandler(name); + + if (func) { + try { + auto result = (*func)(s, args); + s._sendResponse(id, name, result->get()); + } catch (const std::exception &e) { + throw FTL_Error("Exception when attempting to call RPC " << name << " (" << e.what() << ")"); + // FIXME: Send the error in the response. + } + } else { + throw FTL_Error("No binding found for " << name); + } + } else { + throw FTL_Error("Unrecognised message type: " << type); + } } optional<Dispatcher::adaptor_type> ftl::net::Dispatcher::_locateHandler(const std::string &name) const { - auto it_func = funcs_.find(name); - if (it_func == funcs_.end()) { - if (parent_ != nullptr) { - return parent_->_locateHandler(name); - } else { - return {}; - } - } else { - return it_func->second; - } + auto it_func = funcs_.find(name); + if (it_func == funcs_.end()) { + if (parent_ != nullptr) { + return parent_->_locateHandler(name); + } else { + return {}; + } + } else { + return it_func->second; + } } bool ftl::net::Dispatcher::isBound(const std::string &name) const { - return funcs_.find(name) != funcs_.end(); + return funcs_.find(name) != funcs_.end(); } void ftl::net::Dispatcher::dispatch_notification(Peer &s, msgpack::object const &msg) { - notification_t the_call; - msg.convert(the_call); - - // TODO: proper validation of protocol (and responding to it) - // auto &&type = std::get<0>(the_call); - // assert(type == static_cast<uint8_t>(request_type::notification)); - - auto &&name = std::get<1>(the_call); - auto &&args = std::get<2>(the_call); - - auto binding = _locateHandler(name); - - if (binding) { - try { - auto result = (*binding)(s, args); - } catch (const int &e) { - //throw "Exception in bound function"; - throw &e; - } catch (const std::bad_cast &e) { - std::string args_str = ""; - for (size_t i = 0; i < args.via.array.size; i++) { - args_str += object_type_to_string(args.via.array.ptr[i].type); - if ((i + 1) != args.via.array.size) args_str += ", "; - } - throw FTL_Error("Bad cast, got: " << args_str); - - } catch (const std::exception &e) { - throw FTL_Error("Exception for '" << name << "' - " << e.what()); - } - } else { - throw FTL_Error("Missing handler for incoming message (" << name << ")"); - } + notification_t the_call; + msg.convert(the_call); + + // TODO(Nick): proper validation of protocol (and responding to it) + // auto &&type = std::get<0>(the_call); + // assert(type == static_cast<uint8_t>(request_type::notification)); + + auto &&name = std::get<1>(the_call); + auto &&args = std::get<2>(the_call); + + auto binding = _locateHandler(name); + + if (binding) { + try { + auto result = (*binding)(s, args); + } catch (const int &e) { + throw &e; + } catch (const std::bad_cast &e) { + std::string args_str = ""; + for (size_t i = 0; i < args.via.array.size; i++) { + args_str += object_type_to_string(args.via.array.ptr[i].type); + if ((i + 1) != args.via.array.size) args_str += ", "; + } + throw FTL_Error("Bad cast, got: " << args_str); + } catch (const std::exception &e) { + throw FTL_Error("Exception for '" << name << "' - " << e.what()); + } + } else { + throw FTL_Error("Missing handler for incoming message (" << name << ")"); + } } void ftl::net::Dispatcher::enforce_arg_count(std::string const &func, std::size_t found, - std::size_t expected) { - if (found != expected) { - throw FTL_Error("RPC argument missmatch for '" << func << "' - " << found << " != " << expected); - } + std::size_t expected) { + if (found != expected) { + throw FTL_Error("RPC argument missmatch for '" << func << "' - " << found << " != " << expected); + } } void ftl::net::Dispatcher::enforce_unique_name(std::string const &func) { - auto pos = funcs_.find(func); - if (pos != end(funcs_)) { - throw FTL_Error("RPC non unique binding for '" << func << "'"); - } + auto pos = funcs_.find(func); + if (pos != end(funcs_)) { + throw FTL_Error("RPC non unique binding for '" << func << "'"); + } } - diff --git a/src/dispatcher.hpp b/src/dispatcher.hpp index ec2dada9a87e0d59ae086e081d0c109ba4917357..c7f89274ff364e40a59aaefe2ec99cd899afc2a8 100644 --- a/src/dispatcher.hpp +++ b/src/dispatcher.hpp @@ -6,10 +6,6 @@ #pragma once -#include "func_traits.hpp" - -#include <msgpack.hpp> - #include <memory> #include <tuple> #include <functional> @@ -18,6 +14,11 @@ #include <string> #include <unordered_map> #include <optional> +#include <utility> + +#include "func_traits.hpp" + +#include <msgpack.hpp> namespace ftl { @@ -26,39 +27,38 @@ class Peer; } namespace internal { - //! \brief Calls a functor with argument provided directly - template <typename Functor, typename Arg> - auto call(Functor f, Arg &&arg) - -> decltype(f(std::forward<Arg>(arg))) - { - return f(std::forward<Arg>(arg)); - } - - template <typename Functor, typename... Args, std::size_t... I> - decltype(auto) call_helper(Functor func, std::tuple<Args...> &¶ms, - std::index_sequence<I...>) { - return func(std::get<I>(params)...); - } - - template <typename Functor, typename... Args, std::size_t... I> - decltype(auto) call_helper(Functor func, ftl::net::Peer &p, std::tuple<Args...> &¶ms, - std::index_sequence<I...>) { - return func(p, std::get<I>(params)...); - } - - //! \brief Calls a functor with arguments provided as a tuple - template <typename Functor, typename... Args> - decltype(auto) call(Functor f, std::tuple<Args...> &args) { - return call_helper(f, std::forward<std::tuple<Args...>>(args), - std::index_sequence_for<Args...>{}); - } - - //! \brief Calls a functor with arguments provided as a tuple - template <typename Functor, typename... Args> - decltype(auto) call(Functor f, ftl::net::Peer &p, std::tuple<Args...> &args) { - return call_helper(f, p, std::forward<std::tuple<Args...>>(args), - std::index_sequence_for<Args...>{}); - } + //! \brief Calls a functor with argument provided directly + template <typename Functor, typename Arg> + auto call(Functor f, Arg &&arg) + -> decltype(f(std::forward<Arg>(arg))) { + return f(std::forward<Arg>(arg)); + } + + template <typename Functor, typename... Args, std::size_t... I> + decltype(auto) call_helper(Functor func, std::tuple<Args...> &¶ms, + std::index_sequence<I...>) { + return func(std::get<I>(params)...); + } + + template <typename Functor, typename... Args, std::size_t... I> + decltype(auto) call_helper(Functor func, ftl::net::Peer &p, std::tuple<Args...> &¶ms, + std::index_sequence<I...>) { + return func(p, std::get<I>(params)...); + } + + //! \brief Calls a functor with arguments provided as a tuple + template <typename Functor, typename... Args> + decltype(auto) call(Functor f, std::tuple<Args...> &args) { + return call_helper(f, std::forward<std::tuple<Args...>>(args), + std::index_sequence_for<Args...>{}); + } + + //! \brief Calls a functor with arguments provided as a tuple + template <typename Functor, typename... Args> + decltype(auto) call(Functor f, ftl::net::Peer &p, std::tuple<Args...> &args) { + return call_helper(f, p, std::forward<std::tuple<Args...>>(args), + std::index_sequence_for<Args...>{}); + } } namespace net { @@ -69,213 +69,213 @@ namespace net { * arguments). Used by ftl::net::Peer and Universe. */ class Dispatcher { - public: - explicit Dispatcher(Dispatcher *parent=nullptr) : parent_(parent) { - // FIXME: threading and funcs_; hack use large size - funcs_.reserve(1024); - } - - /** - * Primary method by which a peer dispatches a msgpack object that this - * class then decodes to find correct handler and types. - */ - void dispatch(ftl::net::Peer &, const msgpack::object &msg); - - // Without peer object ===================================================== - - /** - * Associate a C++ function woth a string name. Use type traits of that - * function to build a dispatch function that knows how to decode msgpack. - * This is the no arguments and no result case. - */ - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::void_result const &, - ftl::internal::tags::zero_arg const &, - ftl::internal::false_ const &) { - enforce_unique_name(name); - funcs_.insert( - std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { - enforce_arg_count(name, 0, args.via.array.size); - func(); - return std::make_unique<msgpack::object_handle>(); - })); - } - - /** - * Associate a C++ function woth a string name. Use type traits of that - * function to build a dispatch function that knows how to decode msgpack. - * This is the arguments but no result case. - */ - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::void_result const &, - ftl::internal::tags::nonzero_arg const &, - ftl::internal::false_ const &) { - using ftl::internal::func_traits; - using args_type = typename func_traits<F>::args_type; - - enforce_unique_name(name); - funcs_.insert( - std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { - constexpr int args_count = std::tuple_size<args_type>::value; - enforce_arg_count(name, args_count, args.via.array.size); - args_type args_real; - args.convert(args_real); - ftl::internal::call(func, args_real); - return std::make_unique<msgpack::object_handle>(); - })); - } - - /** - * Associate a C++ function woth a string name. Use type traits of that - * function to build a dispatch function that knows how to decode msgpack. - * This is the no arguments but with result case. - */ - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::zero_arg const &, - ftl::internal::false_ const &) { - using ftl::internal::func_traits; - - enforce_unique_name(name); - funcs_.insert(std::make_pair(name, [func, - name](ftl::net::Peer &p, msgpack::object const &args) { - enforce_arg_count(name, 0, args.via.array.size); - auto z = std::make_unique<msgpack::zone>(); - auto result = msgpack::object(func(), *z); - return std::make_unique<msgpack::object_handle>(result, std::move(z)); - })); - } - - /** - * Associate a C++ function woth a string name. Use type traits of that - * function to build a dispatch function that knows how to decode msgpack. - * This is the with arguments and with result case. - */ - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::nonzero_arg const &, - ftl::internal::false_ const &) { - using ftl::internal::func_traits; - using args_type = typename func_traits<F>::args_type; - - enforce_unique_name(name); - funcs_.insert(std::make_pair(name, [func, - name](ftl::net::Peer &p, msgpack::object const &args) { - constexpr int args_count = std::tuple_size<args_type>::value; - enforce_arg_count(name, args_count, args.via.array.size); - args_type args_real; - args.convert(args_real); - auto z = std::make_unique<msgpack::zone>(); - auto result = msgpack::object(ftl::internal::call(func, args_real), *z); - return std::make_unique<msgpack::object_handle>(result, std::move(z)); - })); - } - - // With peer object ======================================================== - - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::void_result const &, - ftl::internal::tags::zero_arg const &, - ftl::internal::true_ const &) { - enforce_unique_name(name); - funcs_.insert( - std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { - enforce_arg_count(name, 0, args.via.array.size); - func(p); - return std::make_unique<msgpack::object_handle>(); - })); - } - - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::void_result const &, - ftl::internal::tags::nonzero_arg const &, - ftl::internal::true_ const &) { - using ftl::internal::func_traits; - using args_type = typename func_traits<F>::args_type; - - enforce_unique_name(name); - funcs_.insert( - std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { - constexpr int args_count = std::tuple_size<args_type>::value; - enforce_arg_count(name, args_count, args.via.array.size); - args_type args_real; - args.convert(args_real); - ftl::internal::call(func, p, args_real); - return std::make_unique<msgpack::object_handle>(); - })); - } - - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::zero_arg const &, - ftl::internal::true_ const &) { - using ftl::internal::func_traits; - - enforce_unique_name(name); - funcs_.insert(std::make_pair(name, [func, - name](ftl::net::Peer &p, msgpack::object const &args) { - enforce_arg_count(name, 0, args.via.array.size); - auto z = std::make_unique<msgpack::zone>(); - auto result = msgpack::object(func(p), *z); - return std::make_unique<msgpack::object_handle>(result, std::move(z)); - })); - } - - template <typename F> - void bind(std::string const &name, F func, - ftl::internal::tags::nonvoid_result const &, - ftl::internal::tags::nonzero_arg const &, - ftl::internal::true_ const &) { - using ftl::internal::func_traits; - using args_type = typename func_traits<F>::args_type; - - enforce_unique_name(name); - funcs_.insert(std::make_pair(name, [func, - name](ftl::net::Peer &p, msgpack::object const &args) { - constexpr int args_count = std::tuple_size<args_type>::value; - enforce_arg_count(name, args_count, args.via.array.size); - args_type args_real; - args.convert(args_real); - auto z = std::make_unique<msgpack::zone>(); - auto result = msgpack::object(ftl::internal::call(func, p, args_real), *z); - return std::make_unique<msgpack::object_handle>(result, std::move(z)); - })); - } - - //========================================================================== - - /** - * Remove a previous bound function by name. - */ - void unbind(const std::string &name) { - auto i = funcs_.find(name); - if (i != funcs_.end()) { - funcs_.erase(i); - } - } - - /** - * @return All bound function names. - */ - std::vector<std::string> getBindings() const; - - /** - * @param name Function name. - * @return True if the given name is bound to a function. - */ - bool isBound(const std::string &name) const; - - - //==== Types =============================================================== - - using adaptor_type = std::function<std::unique_ptr<msgpack::object_handle>( + public: + explicit Dispatcher(Dispatcher *parent = nullptr) : parent_(parent) { + // FIXME: threading and funcs_; hack use large size + funcs_.reserve(1024); + } + + /** + * Primary method by which a peer dispatches a msgpack object that this + * class then decodes to find correct handler and types. + */ + void dispatch(ftl::net::Peer &, const msgpack::object &msg); + + // Without peer object ===================================================== + + /** + * Associate a C++ function woth a string name. Use type traits of that + * function to build a dispatch function that knows how to decode msgpack. + * This is the no arguments and no result case. + */ + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::false_ const &) { + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + func(); + return std::make_unique<msgpack::object_handle>(); + })); + } + + /** + * Associate a C++ function woth a string name. Use type traits of that + * function to build a dispatch function that knows how to decode msgpack. + * This is the arguments but no result case. + */ + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::false_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + ftl::internal::call(func, args_real); + return std::make_unique<msgpack::object_handle>(); + })); + } + + /** + * Associate a C++ function woth a string name. Use type traits of that + * function to build a dispatch function that knows how to decode msgpack. + * This is the no arguments but with result case. + */ + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::false_ const &) { + using ftl::internal::func_traits; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(func(), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + /** + * Associate a C++ function woth a string name. Use type traits of that + * function to build a dispatch function that knows how to decode msgpack. + * This is the with arguments and with result case. + */ + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::false_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(ftl::internal::call(func, args_real), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + // With peer object ======================================================== + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::true_ const &) { + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + func(p); + return std::make_unique<msgpack::object_handle>(); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::void_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert( + std::make_pair(name, [func, name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + ftl::internal::call(func, p, args_real); + return std::make_unique<msgpack::object_handle>(); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::zero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + enforce_arg_count(name, 0, args.via.array.size); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(func(p), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + template <typename F> + void bind(std::string const &name, F func, + ftl::internal::tags::nonvoid_result const &, + ftl::internal::tags::nonzero_arg const &, + ftl::internal::true_ const &) { + using ftl::internal::func_traits; + using args_type = typename func_traits<F>::args_type; + + enforce_unique_name(name); + funcs_.insert(std::make_pair(name, [func, + name](ftl::net::Peer &p, msgpack::object const &args) { + constexpr int args_count = std::tuple_size<args_type>::value; + enforce_arg_count(name, args_count, args.via.array.size); + args_type args_real; + args.convert(args_real); + auto z = std::make_unique<msgpack::zone>(); + auto result = msgpack::object(ftl::internal::call(func, p, args_real), *z); + return std::make_unique<msgpack::object_handle>(result, std::move(z)); + })); + } + + //========================================================================== + + /** + * Remove a previous bound function by name. + */ + void unbind(const std::string &name) { + auto i = funcs_.find(name); + if (i != funcs_.end()) { + funcs_.erase(i); + } + } + + /** + * @return All bound function names. + */ + std::vector<std::string> getBindings() const; + + /** + * @param name Function name. + * @return True if the given name is bound to a function. + */ + bool isBound(const std::string &name) const; + + + //==== Types =============================================================== + + using adaptor_type = std::function<std::unique_ptr<msgpack::object_handle>( ftl::net::Peer &, msgpack::object const &)>; //! \brief This is the type of messages as per the msgpack-rpc spec. @@ -287,20 +287,20 @@ class Dispatcher { using response_t = std::tuple<uint32_t, uint32_t, std::string, msgpack::object>; - private: - Dispatcher *parent_; - std::unordered_map<std::string, adaptor_type> funcs_; + private: + Dispatcher *parent_; + std::unordered_map<std::string, adaptor_type> funcs_; - std::optional<adaptor_type> _locateHandler(const std::string &name) const; + std::optional<adaptor_type> _locateHandler(const std::string &name) const; - static void enforce_arg_count(std::string const &func, std::size_t found, + static void enforce_arg_count(std::string const &func, std::size_t found, std::size_t expected); void enforce_unique_name(std::string const &func); - void dispatch_call(ftl::net::Peer &, const msgpack::object &msg); - void dispatch_notification(ftl::net::Peer &, msgpack::object const &msg); + void dispatch_call(ftl::net::Peer &, const msgpack::object &msg); + void dispatch_notification(ftl::net::Peer &, msgpack::object const &msg); }; -} -} +} // namespace net +} // namespace ftl diff --git a/src/exception.cpp b/src/exception.cpp index 9a337a073fda866ab495e64644d189368faba460..8ab442cc041bde3464a3c3a186c2f3f3557ffb18 100644 --- a/src/exception.cpp +++ b/src/exception.cpp @@ -15,19 +15,18 @@ #include <cxxabi.h> std::string demangle(const char* name) { - if (!name) { - return "[unknown symbol]"; - } - int status; - char* demangled = abi::__cxa_demangle(name, NULL, 0, &status); - if (!demangled) { - return std::string(name); - } - else { - auto result = std::string(demangled); - free(demangled); - return result; - } + if (!name) { + return "[unknown symbol]"; + } + int status; + char* demangled = abi::__cxa_demangle(name, NULL, 0, &status); + if (!demangled) { + return std::string(name); + } else { + auto result = std::string(demangled); + free(demangled); + return result; + } } #endif @@ -36,71 +35,70 @@ using ftl::exception; using std::string; string addr_to_string(const void* addr) { - std::stringstream ss; - ss << addr; - return ss.str(); + std::stringstream ss; + ss << addr; + return ss.str(); } #ifdef __GNUC__ string exception::decode_backtrace() const { - string result; - // backtrace_symbols() as fallback (no data from dladdr()) - char **messages = backtrace_symbols(trace_, trace_size_); - - if (!messages) { - return string("[bt] no trace"); - } - - /* skip first stack frame (points here) */ - for (int i=1; i < trace_size_; ++i) { - result += string("[bt] #") + std::to_string(i-1) - + string(TRACE_SIZE_MAX_/10 - (i-1)/10, ' ') - + string(" "); - - Dl_info info; - if (dladdr(trace_[i], &info) && info.dli_saddr) { - auto name = demangle(info.dli_sname); - string fname = info.dli_fname ? info.dli_fname: "[unknown file]"; - - result += fname + - + " " - + " [" + addr_to_string(info.dli_saddr) + "]" // exact address of symbol - + string(", in ") - + name; - } - else { - result += messages[i]; - } - result += "\n"; - } - - free(messages); - return result; + string result; + // backtrace_symbols() as fallback (no data from dladdr()) + char **messages = backtrace_symbols(trace_, trace_size_); + + if (!messages) { + return string("[bt] no trace"); + } + + /* skip first stack frame (points here) */ + for (int i=1; i < trace_size_; ++i) { + result += string("[bt] #") + std::to_string(i-1) + + string(TRACE_SIZE_MAX_/10 - (i-1)/10, ' ') + + string(" "); + + Dl_info info; + if (dladdr(trace_[i], &info) && info.dli_saddr) { + auto name = demangle(info.dli_sname); + string fname = info.dli_fname ? info.dli_fname: "[unknown file]"; + + result += fname + + + " " + + " [" + addr_to_string(info.dli_saddr) + "]" // exact address of symbol + + string(", in ") + + name; + } else { + result += messages[i]; + } + result += "\n"; + } + + free(messages); + return result; } #else string exception::decode_backtrace() const { - return string(); + return string(); } #endif exception::exception(const char *msg) : msg_(msg), processed_(false) { - #ifdef __GNUC__ - trace_size_ = backtrace(trace_, TRACE_SIZE_MAX_); - #endif + #ifdef __GNUC__ + trace_size_ = backtrace(trace_, TRACE_SIZE_MAX_); + #endif } exception::exception(const ftl::Formatter &msg) : msg_(msg.str()), processed_(false) { - #ifdef __GNUC__ - trace_size_ = backtrace(trace_, TRACE_SIZE_MAX_); - #endif + #ifdef __GNUC__ + trace_size_ = backtrace(trace_, TRACE_SIZE_MAX_); + #endif } exception::~exception() { - if (!processed_) { // what() or ignore() have not been called. - LOG(ERROR) << "Unhandled exception: " << what(); - #ifdef __GNUC__ - LOG(ERROR) << "Trace:\n" << decode_backtrace(); - #endif - } + if (!processed_) { // what() or ignore() have not been called. + LOG(ERROR) << "Unhandled exception: " << what(); + #ifdef __GNUC__ + LOG(ERROR) << "Trace:\n" << decode_backtrace(); + #endif + } } diff --git a/src/func_traits.hpp b/src/func_traits.hpp index 1008f364d514a8652858882bc0883d136dbade9e..e2ce5e57e2ebd36340e853c18d4f40e79179c2f1 100644 --- a/src/func_traits.hpp +++ b/src/func_traits.hpp @@ -43,14 +43,22 @@ struct nonzero_arg {}; struct void_result {}; struct nonvoid_result {}; -template <int N> struct arg_count_trait { typedef nonzero_arg type; }; +template <int N> struct arg_count_trait { + typedef nonzero_arg type; +}; -template <> struct arg_count_trait<0> { typedef zero_arg type; }; +template <> struct arg_count_trait<0> { + typedef zero_arg type; +}; -template <typename T> struct result_trait { typedef nonvoid_result type; }; +template <typename T> struct result_trait { + typedef nonvoid_result type; +}; -template <> struct result_trait<void> { typedef void_result type; }; -} +template <> struct result_trait<void> { + typedef void_result type; +}; +} // namespace tags //! \brief Provides a small function traits implementation that //! works with a reasonably large set of functors. @@ -63,7 +71,7 @@ struct func_traits<R (C::*)(Args...)> : func_traits<R (*)(Args...)> {}; template <typename C, typename R, typename... Args> struct func_traits<R (C::*)(Args...) const> : func_traits<R (*)(Args...)> {}; -template <typename R, typename... Args> struct func_traits<R (*)(ftl::net::Peer &,Args...)> { +template <typename R, typename... Args> struct func_traits<R (*)(ftl::net::Peer &, Args...)> { using result_type = R; using arg_count = std::integral_constant<std::size_t, sizeof...(Args)>; using args_type = std::tuple<typename std::decay<Args>::type...>; @@ -75,8 +83,8 @@ template <typename R, typename... Args> struct func_traits<R (*)(Args...)> { using args_type = std::tuple<typename std::decay<Args>::type...>; }; -//template <typename T> -//auto bindThis(F f, T t) { return [f,t]()t.f(42, std::forward<decltype(arg)>(arg)); } +// template <typename T> +// auto bindThis(F f, T t) { return [f,t]()t.f(42, std::forward<decltype(arg)>(arg)); } template <typename T> struct func_kind_info : func_kind_info<decltype(&T::operator())> {}; @@ -84,23 +92,23 @@ struct func_kind_info : func_kind_info<decltype(&T::operator())> {}; template <typename C, typename R, typename... Args> struct func_kind_info<R (C::*)(Args...)> : func_kind_info<R (*)(Args...)> {}; -//template <typename R, typename... Args> -//struct func_kind_info<std::_Bind<R(Args...)>> : func_kind_info<R(*)(Args...)> {}; +// template <typename R, typename... Args> +// struct func_kind_info<std::_Bind<R(Args...)>> : func_kind_info<R(*)(Args...)> {}; template <typename C, typename R, typename... Args> struct func_kind_info<R (C::*)(Args...) const> : func_kind_info<R (*)(Args...)> {}; -template <typename R, typename... Args> struct func_kind_info<R (*)(ftl::net::Peer &,Args...)> { +template <typename R, typename... Args> struct func_kind_info<R (*)(ftl::net::Peer &, Args...)> { typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind; typedef typename tags::result_trait<R>::type result_kind; - typedef true_ has_peer; + typedef true_ has_peer; }; template <typename R, typename... Args> struct func_kind_info<R (*)(Args...)> { typedef typename tags::arg_count_trait<sizeof...(Args)>::type args_kind; typedef typename tags::result_trait<R>::type result_kind; - typedef false_ has_peer; + typedef false_ has_peer; }; template <typename F> using is_zero_arg = is_zero<func_traits<F>::arg_count>; @@ -111,8 +119,7 @@ using is_single_arg = template <typename F> using is_void_result = std::is_void<typename func_traits<F>::result_type>; -} -} +} // namespace internal +} // namespace ftl #endif /* end of include guard: FUNC_TRAITS_H_HWIWA6G0 */ - diff --git a/src/handlers.hpp b/src/handlers.hpp index c8fad23a27166cf696206ef981e6d8e4b9f720b4..7c55dcac3ef85cea4ffa70f8b19937ed7892c13f 100644 --- a/src/handlers.hpp +++ b/src/handlers.hpp @@ -8,6 +8,7 @@ #include <functional> #include <memory> +#include <string> namespace ftl { namespace net { @@ -24,6 +25,5 @@ using ErrorHandler = std::function<void(std::shared_ptr<Socket>, int)>; using ConnectHandler = std::function<void(std::shared_ptr<Socket> &)>; using DisconnectHandler = std::function<void(std::shared_ptr<Socket>)>; -}; -}; - +} // namespace net +} // namespace ftl diff --git a/src/node.cpp b/src/node.cpp index 445a5041809f7e82b575dfc4c6f0be158f6d6428..1aa9d11ded10b9e013700861f34b5224a7caa349 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -1,3 +1,9 @@ +/** + * @file node.cpp + * @copyright Copyright (c) 2020 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol/node.hpp> #include "peer.hpp" diff --git a/src/peer.cpp b/src/peer.cpp index efe0af6af772c4b04deed22ec3ca7aaf635bb25c..040f7090d1582a3063e1a916ac49c81406387847 100644 --- a/src/peer.cpp +++ b/src/peer.cpp @@ -4,6 +4,15 @@ * @author Nicolas Pope */ +#include <iostream> +#include <memory> +#include <algorithm> +#include <tuple> +#include <chrono> +#include <vector> +#include <utility> +#include <string> + #include <ftl/lib/loguru.hpp> #include <ftl/lib/ctpl_stl.hpp> @@ -12,7 +21,6 @@ #include <ftl/uri.hpp> #include <ftl/time.hpp> #include "peer.hpp" -//#include <ftl/config.h> #include "protocol/connection.hpp" @@ -20,13 +28,6 @@ using ftl::net::internal::SocketConnection; #include "universe.hpp" -#include <iostream> -#include <memory> -#include <algorithm> -#include <tuple> -#include <chrono> -#include <vector> - using std::tuple; using std::get; using ftl::net::Peer; @@ -39,545 +40,533 @@ using ftl::net::Callback; using std::vector; using ftl::protocol::NodeStatus; using ftl::protocol::NodeType; +using ftl::protocol::Error; std::atomic_int Peer::rpcid__ = 0; int Peer::_socket() const { - if (sock_->is_valid()) { - return sock_->fd(); - } else { - return INVALID_SOCKET; - } + if (sock_->is_valid()) { + return sock_->fd(); + } else { + return INVALID_SOCKET; + } } bool Peer::isConnected() const { - return sock_->is_valid() && (status_ == NodeStatus::kConnected); + return sock_->is_valid() && (status_ == NodeStatus::kConnected); } bool Peer::isValid() const { - return sock_ && sock_->is_valid() && ((status_ == NodeStatus::kConnected) || (status_ == NodeStatus::kConnecting)); + return sock_ && sock_->is_valid() && ((status_ == NodeStatus::kConnected) || (status_ == NodeStatus::kConnecting)); } void Peer::_set_socket_options() { - CHECK(net_); - CHECK(sock_); - - // error printed by set methods (return value ignored) - sock_->set_send_buffer_size(net_->getSendBufferSize(sock_->scheme())); - sock_->set_recv_buffer_size(net_->getRecvBufferSize(sock_->scheme())); - - DLOG(1) << "send buffer size: " << (sock_->get_send_buffer_size() >> 10) << "KiB, " - << "recv buffer size: " << (sock_->get_recv_buffer_size() >> 10) << "KiB"; + CHECK(net_); + CHECK(sock_); + + // error printed by set methods (return value ignored) + sock_->set_send_buffer_size(net_->getSendBufferSize(sock_->scheme())); + sock_->set_recv_buffer_size(net_->getRecvBufferSize(sock_->scheme())); + + DLOG(1) << "send buffer size: " << (sock_->get_send_buffer_size() >> 10) << "KiB, " + << "recv buffer size: " << (sock_->get_recv_buffer_size() >> 10) << "KiB"; } void Peer::_send_handshake() { - DLOG(INFO) << "(" << (outgoing_ ? "connecting" : "listening") - << " peer) handshake sent, status: " - << (isConnected() ? "connected" : "connecting"); - - send("__handshake__", ftl::net::kMagic, ftl::net::kVersion, net_->id()); + DLOG(INFO) << "(" << (outgoing_ ? "connecting" : "listening") + << " peer) handshake sent, status: " + << (isConnected() ? "connected" : "connecting"); + + send("__handshake__", ftl::net::kMagic, ftl::net::kVersion, net_->id()); } -void Peer::_process_handshake(uint64_t magic, uint32_t version, UUID pid) { - /** Handshake protocol: - * (1). Listening side accepts connection and sends handshake. - * (2). Connecting side acknowledges by replying with own handshake and - * sets status to kConnected. - * (3). Listening side receives handshake and sets status to kConnected. - */ - if (magic != ftl::net::kMagic) { - net_->_notifyError(this, ftl::protocol::Error::kBadHandshake, "invalid magic during handshake"); - _close(reconnect_on_protocol_error_); - } else { - if (version != ftl::net::kVersion) LOG(WARNING) << "net protocol using different versions!"; - - DLOG(INFO) << "(" << (outgoing_ ? "connecting" : "listening") - << " peer) handshake received from remote for " << pid.to_string(); - - status_ = NodeStatus::kConnected; - version_ = version; - peerid_ = pid; - - if (outgoing_) { - // only outgoing connection replies with handshake, listening socket - // sends initial handshake on connect - _send_handshake(); - } - - ++connection_count_; - net_->_notifyConnect(this); - } +void Peer::_process_handshake(uint64_t magic, uint32_t version, const UUID &pid) { + /** Handshake protocol: + * (1). Listening side accepts connection and sends handshake. + * (2). Connecting side acknowledges by replying with own handshake and + * sets status to kConnected. + * (3). Listening side receives handshake and sets status to kConnected. + */ + if (magic != ftl::net::kMagic) { + net_->_notifyError(this, ftl::protocol::Error::kBadHandshake, "invalid magic during handshake"); + _close(reconnect_on_protocol_error_); + } else { + if (version != ftl::net::kVersion) LOG(WARNING) << "net protocol using different versions!"; + + DLOG(INFO) << "(" << (outgoing_ ? "connecting" : "listening") + << " peer) handshake received from remote for " << pid.to_string(); + + status_ = NodeStatus::kConnected; + version_ = version; + peerid_ = pid; + + if (outgoing_) { + // only outgoing connection replies with handshake, listening socket + // sends initial handshake on connect + _send_handshake(); + } + + ++connection_count_; + net_->_notifyConnect(this); + } } void Peer::_bind_rpc() { - // Install return handshake handler. - bind("__handshake__", [this](uint64_t magic, uint32_t version, UUID pid) { - _process_handshake(magic, version, pid); - }); - - bind("__disconnect__", [this]() { - close(reconnect_on_remote_disconnect_); - DLOG(1) << "peer elected to disconnect: " << id().to_string(); - }); - - bind("__ping__", [this]() { - return ftl::time::get_time(); - }); - + // Install return handshake handler. + bind("__handshake__", [this](uint64_t magic, uint32_t version, const UUID &pid) { + _process_handshake(magic, version, pid); + }); + + bind("__disconnect__", [this]() { + close(reconnect_on_remote_disconnect_); + DLOG(1) << "peer elected to disconnect: " << id().to_string(); + }); + + bind("__ping__", [this]() { + return ftl::time::get_time(); + }); } Peer::Peer(std::unique_ptr<internal::SocketConnection> s, Universe* u, Dispatcher* d) : - outgoing_(false), - local_id_(0), - uri_("0"), - status_(NodeStatus::kConnecting), - can_reconnect_(false), - net_(u), - sock_(std::move(s)), - disp_(std::make_unique<Dispatcher>(d)) { - - /* Incoming connection constructor */ - - CHECK(sock_) << "incoming SocketConnection pointer null"; - _set_socket_options(); - _updateURI(); - _bind_rpc(); - ++net_->peer_instances_; + outgoing_(false), + local_id_(0), + uri_("0"), + status_(NodeStatus::kConnecting), + can_reconnect_(false), + net_(u), + sock_(std::move(s)), + disp_(std::make_unique<Dispatcher>(d)) { + /* Incoming connection constructor */ + + CHECK(sock_) << "incoming SocketConnection pointer null"; + _set_socket_options(); + _updateURI(); + _bind_rpc(); + ++net_->peer_instances_; } -Peer::Peer(const ftl::URI& uri, Universe *u, Dispatcher *d) : - outgoing_(true), - local_id_(0), - uri_(uri), - status_(NodeStatus::kInvalid), - can_reconnect_(true), - net_(u), - disp_(std::make_unique<Dispatcher>(d)) { - - /* Outgoing connection constructor */ - - _bind_rpc(); - _connect(); - ++net_->peer_instances_; +Peer::Peer(const ftl::URI& uri, Universe *u, Dispatcher *d) : + outgoing_(true), + local_id_(0), + uri_(uri), + status_(NodeStatus::kInvalid), + can_reconnect_(true), + net_(u), + disp_(std::make_unique<Dispatcher>(d)) { + /* Outgoing connection constructor */ + + _bind_rpc(); + _connect(); + ++net_->peer_instances_; } void Peer::start() { - if (outgoing_) { - // Connect needs to be in constructor - } else { - _send_handshake(); - } + if (outgoing_) { + // Connect needs to be in constructor + } else { + _send_handshake(); + } } void Peer::_connect() { - sock_ = ftl::net::internal::createConnection(uri_); // throws on bad uri - _set_socket_options(); - sock_->connect(uri_); // throws on error - status_ = NodeStatus::kConnecting; + sock_ = ftl::net::internal::createConnection(uri_); // throws on bad uri + _set_socket_options(); + sock_->connect(uri_); // throws on error + status_ = NodeStatus::kConnecting; } /** Called from ftl::Universe::_periodic() */ bool Peer::reconnect() { + if (status_ != NodeStatus::kConnecting || !can_reconnect_) return false; - if (status_ != NodeStatus::kConnecting || !can_reconnect_) return false; - - URI uri(uri_); + URI uri(uri_); - DLOG(INFO) << "Reconnecting to " << uri_.to_string() << " ..."; + DLOG(INFO) << "Reconnecting to " << uri_.to_string() << " ..."; - // First, ensure all stale jobs and buffer data are removed. - while (job_count_ > 0 && ftl::pool.size() > 0) { - DLOG(1) << "Waiting on peer jobs before reconnect " << job_count_; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - } - recv_buf_.remove_nonparsed_buffer(); - recv_buf_.reset(); + // First, ensure all stale jobs and buffer data are removed. + while (job_count_ > 0 && ftl::pool.size() > 0) { + DLOG(1) << "Waiting on peer jobs before reconnect " << job_count_; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + recv_buf_.remove_nonparsed_buffer(); + recv_buf_.reset(); - try { - _connect(); - return true; - - } catch(const std::exception& ex) { - net_->_notifyError(this, ftl::protocol::Error::kReconnectionFailed, ex.what()); - } + try { + _connect(); + return true; + } catch(const std::exception& ex) { + net_->_notifyError(this, ftl::protocol::Error::kReconnectionFailed, ex.what()); + } - close(true); - return false; + close(true); + return false; } void Peer::_updateURI() { - // should be same as provided uri for connecting sockets, for connections - // created by listening socket should generate some meaningful value - uri_ = sock_->uri(); + // should be same as provided uri for connecting sockets, for connections + // created by listening socket should generate some meaningful value + uri_ = sock_->uri(); } -void Peer::rawClose() { - UNIQUE_LOCK(send_mtx_, lk_send); - //UNIQUE_LOCK(recv_mtx_, lk_recv); - sock_->close(); - status_ = NodeStatus::kDisconnected; +void Peer::rawClose() { + UNIQUE_LOCK(send_mtx_, lk_send); + // UNIQUE_LOCK(recv_mtx_, lk_recv); + sock_->close(); + status_ = NodeStatus::kDisconnected; } void Peer::close(bool retry) { - // Attempt to inform about disconnect - if (sock_->is_valid() && status_ == NodeStatus::kConnected) { - send("__disconnect__"); - } + // Attempt to inform about disconnect + if (sock_->is_valid() && status_ == NodeStatus::kConnected) { + send("__disconnect__"); + } - UNIQUE_LOCK(send_mtx_, lk_send); - //UNIQUE_LOCK(recv_mtx_, lk_recv); + UNIQUE_LOCK(send_mtx_, lk_send); + // UNIQUE_LOCK(recv_mtx_, lk_recv); - _close(retry); + _close(retry); } void Peer::_close(bool retry) { - if (status_ != NodeStatus::kConnected && status_ != NodeStatus::kConnecting) return; - - // Attempt auto reconnect? - if (retry && can_reconnect_) { - status_ = NodeStatus::kReconnecting; - } else { - status_ = NodeStatus::kDisconnected; - } - - if (sock_->is_valid()) { - net_->_notifyDisconnect(this); - sock_->close(); - } + if (status_ != NodeStatus::kConnected && status_ != NodeStatus::kConnecting) return; + + // Attempt auto reconnect? + if (retry && can_reconnect_) { + status_ = NodeStatus::kReconnecting; + } else { + status_ = NodeStatus::kDisconnected; + } + + if (sock_->is_valid()) { + net_->_notifyDisconnect(this); + sock_->close(); + } } bool Peer::socketError() { - int errcode = sock_->getSocketError(); + int errcode = sock_->getSocketError(); - if (!sock_->is_fatal(errcode)) return false; + if (!sock_->is_fatal(errcode)) return false; - if (errcode == ECONNRESET) { - _close(reconnect_on_socket_error_); - return true; - } + if (errcode == ECONNRESET) { + _close(reconnect_on_socket_error_); + return true; + } - net_->_notifyError(this, ftl::protocol::Error::kSocketError, std::string("Socket error: ") + std::to_string(errcode)); - _close(reconnect_on_socket_error_); - return true; + net_->_notifyError(this, Error::kSocketError, std::string("Socket error: ") + std::to_string(errcode)); + _close(reconnect_on_socket_error_); + return true; } -void Peer::error(int e) { - -} +void Peer::error(int e) {} NodeType Peer::getType() const { - if ((uri_.getScheme() == URI::SCHEME_WS) - || (uri_.getScheme() == URI::SCHEME_WSS)) { - - return NodeType::kWebService; - } - return NodeType::kNode; + if ((uri_.getScheme() == URI::SCHEME_WS) + || (uri_.getScheme() == URI::SCHEME_WSS)) { + return NodeType::kWebService; + } + return NodeType::kNode; } void Peer::_createJob() { - ++job_count_; - - ftl::pool.push([this](int id) { - try { - _data(); - } catch (const std::exception &e) { - net_->_notifyError(this, ftl::protocol::Error::kUnknown, e.what()); - } - --job_count_; - }); + ++job_count_; + + ftl::pool.push([this](int id) { + try { + _data(); + } catch (const std::exception &e) { + net_->_notifyError(this, ftl::protocol::Error::kUnknown, e.what()); + } + --job_count_; + }); } void Peer::data() { - if (!sock_->is_valid()) { return; } - - int rc = 0; - - // Only need to lock and reserve buffer if there isn't enough - if (recv_buf_.buffer_capacity() < kMaxMessage) { - UNIQUE_LOCK(recv_mtx_,lk); - recv_buf_.reserve_buffer(kMaxMessage); - } - - int cap = static_cast<int>(recv_buf_.buffer_capacity()); - - try { - rc = sock_->recv(recv_buf_.buffer(), recv_buf_.buffer_capacity()); - - if (rc >= cap - 1) { - net_->_notifyError(this, ftl::protocol::Error::kBufferSize, "Too much data received"); - // TODO: Increase the buffer size next time - } - if (cap < (kMaxMessage / 10)) { - net_->_notifyError(this, ftl::protocol::Error::kBufferSize, "Buffer is at capacity"); - } - - } catch (std::exception& ex) { - net_->_notifyError(this, ftl::protocol::Error::kSocketError, ex.what()); - close(reconnect_on_socket_error_); - return; - - } - - if (rc == 0) { // retry later - CHECK(sock_->is_valid() == false); - //close(reconnect_on_socket_error_); - return; - } - if (rc < 0) { // error so close peer - sock_->close(); - close(reconnect_on_socket_error_); - return; - } - - // May possibly need locking - recv_buf_.buffer_consumed(rc); - - recv_checked_.clear(); - if (!already_processing_.test_and_set()) { - //lk.unlock(); - _createJob(); - } + if (!sock_->is_valid()) { return; } + + int rc = 0; + + // Only need to lock and reserve buffer if there isn't enough + if (recv_buf_.buffer_capacity() < kMaxMessage) { + UNIQUE_LOCK(recv_mtx_, lk); + recv_buf_.reserve_buffer(kMaxMessage); + } + + int cap = static_cast<int>(recv_buf_.buffer_capacity()); + + try { + rc = sock_->recv(recv_buf_.buffer(), recv_buf_.buffer_capacity()); + + if (rc >= cap - 1) { + net_->_notifyError(this, Error::kBufferSize, "Too much data received"); + // TODO(Nick): Increase the buffer size next time + } + if (cap < (kMaxMessage / 10)) { + net_->_notifyError(this, Error::kBufferSize, "Buffer is at capacity"); + } + } catch (std::exception& ex) { + net_->_notifyError(this, Error::kSocketError, ex.what()); + close(reconnect_on_socket_error_); + return; + } + + if (rc == 0) { // retry later + CHECK(sock_->is_valid() == false); + // close(reconnect_on_socket_error_); + return; + } + if (rc < 0) { // error so close peer + sock_->close(); + close(reconnect_on_socket_error_); + return; + } + + // May possibly need locking + recv_buf_.buffer_consumed(rc); + + recv_checked_.clear(); + if (!already_processing_.test_and_set()) { + // lk.unlock(); + _createJob(); + } } bool Peer::_has_next() { + if (!sock_->is_valid()) { return false; } - if (!sock_->is_valid()) { return false; } - - bool has_next = true; - // buffer might contain non-msgpack data (headers etc). check with - // prepare_next() and skip if necessary - size_t skip; - auto buffer = recv_buf_.nonparsed_buffer(); - auto buffer_len = recv_buf_.nonparsed_size(); - has_next = sock_->prepare_next(buffer, buffer_len, skip); + bool has_next = true; + // buffer might contain non-msgpack data (headers etc). check with + // prepare_next() and skip if necessary + size_t skip; + auto buffer = recv_buf_.nonparsed_buffer(); + auto buffer_len = recv_buf_.nonparsed_size(); + has_next = sock_->prepare_next(buffer, buffer_len, skip); - if (has_next) { recv_buf_.skip_nonparsed_buffer(skip); } + if (has_next) { recv_buf_.skip_nonparsed_buffer(skip); } - return has_next; + return has_next; } bool Peer::_data() { - // lock before trying to acquire handle to buffer - //UNIQUE_LOCK(recv_mtx_, lk); - - // msgpack::object is valid as long as handle is - msgpack::object_handle msg_handle; - - try { - recv_checked_.test_and_set(); - - UNIQUE_LOCK(recv_mtx_,lk); - bool has_next = _has_next() && recv_buf_.next(msg_handle); - lk.unlock(); - - if (!has_next) { - already_processing_.clear(); - if (!recv_checked_.test_and_set() && !already_processing_.test_and_set()) { - return _data(); - } - return false; - } - } catch (const std::exception& ex) { - net_->_notifyError(this, ftl::protocol::Error::kPacketFailure, ex.what()); - _close(reconnect_on_protocol_error_); - return false; - } - - //lk.unlock(); - - msgpack::object obj = msg_handle.get(); - - if (status_ == NodeStatus::kConnecting) { - // If not connected, must lock to make sure no other thread performs this step - //lk.lock(); - - // Verify still not connected after lock - //if (status_ == NodeStatus::kConnecting) { - // First message must be a handshake - try { - tuple<uint32_t, std::string, msgpack::object> hs; - obj.convert(hs); - - if (get<1>(hs) != "__handshake__") { - DLOG(WARNING) << "Missing handshake - got '" << get<1>(hs) << "'"; - - // Allow a small delay in case another thread is doing the handshake - //lk.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (status_ == NodeStatus::kConnecting) { - net_->_notifyError(this, ftl::protocol::Error::kMissingHandshake, "failed to get handshake"); - close(reconnect_on_protocol_error_); - //lk.lock(); - return false; - } - } else { - // Must handle immediately with no other thread able - // to read next message before completion. - // The handshake handler must not block. - - try { - disp_->dispatch(*this, obj); - } catch (const std::exception &e) { - net_->_notifyError(this, ftl::protocol::Error::kDispatchFailed, e.what()); - } - - _createJob(); - return true; - } - } catch(...) { - DLOG(WARNING) << "Bad first message format... waiting"; - // Allow a small delay in case another thread is doing the handshake - - //lk.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (status_ == NodeStatus::kConnecting) { - net_->_notifyError(this, ftl::protocol::Error::kMissingHandshake, "failed to get handshake"); - close(reconnect_on_protocol_error_); - return false; - } - } - //} else { - //lk.unlock(); - //} - } - - // Process more data... - _createJob(); - - try { - disp_->dispatch(*this, obj); - } catch (const std::exception &e) { - net_->_notifyError(this, ftl::protocol::Error::kDispatchFailed, e.what()); - } - - // Lock again before freeing msg_handle (destruction order). - // msgpack::object_handle destructor modifies recv_buffer_ - //lk.lock(); - return true; + // lock before trying to acquire handle to buffer + // UNIQUE_LOCK(recv_mtx_, lk); + + // msgpack::object is valid as long as handle is + msgpack::object_handle msg_handle; + + try { + recv_checked_.test_and_set(); + + UNIQUE_LOCK(recv_mtx_, lk); + bool has_next = _has_next() && recv_buf_.next(msg_handle); + lk.unlock(); + + if (!has_next) { + already_processing_.clear(); + if (!recv_checked_.test_and_set() && !already_processing_.test_and_set()) { + return _data(); + } + return false; + } + } catch (const std::exception& ex) { + net_->_notifyError(this, ftl::protocol::Error::kPacketFailure, ex.what()); + _close(reconnect_on_protocol_error_); + return false; + } + + // lk.unlock(); + + msgpack::object obj = msg_handle.get(); + + if (status_ == NodeStatus::kConnecting) { + // If not connected, must lock to make sure no other thread performs this step + // lk.lock(); + + // Verify still not connected after lock + // if (status_ == NodeStatus::kConnecting) { + // First message must be a handshake + try { + tuple<uint32_t, std::string, msgpack::object> hs; + obj.convert(hs); + + if (get<1>(hs) != "__handshake__") { + DLOG(WARNING) << "Missing handshake - got '" << get<1>(hs) << "'"; + + // Allow a small delay in case another thread is doing the handshake + // lk.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (status_ == NodeStatus::kConnecting) { + net_->_notifyError(this, Error::kMissingHandshake, "failed to get handshake"); + close(reconnect_on_protocol_error_); + // lk.lock(); + return false; + } + } else { + // Must handle immediately with no other thread able + // to read next message before completion. + // The handshake handler must not block. + + try { + disp_->dispatch(*this, obj); + } catch (const std::exception &e) { + net_->_notifyError(this, ftl::protocol::Error::kDispatchFailed, e.what()); + } + + _createJob(); + return true; + } + } catch(...) { + DLOG(WARNING) << "Bad first message format... waiting"; + // Allow a small delay in case another thread is doing the handshake + + // lk.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (status_ == NodeStatus::kConnecting) { + net_->_notifyError(this, Error::kMissingHandshake, "failed to get handshake"); + close(reconnect_on_protocol_error_); + return false; + } + } + // } else { + // lk.unlock(); + // } + } + + // Process more data... + _createJob(); + + try { + disp_->dispatch(*this, obj); + } catch (const std::exception &e) { + net_->_notifyError(this, Error::kDispatchFailed, e.what()); + } + + // Lock again before freeing msg_handle (destruction order). + // msgpack::object_handle destructor modifies recv_buffer_ + // lk.lock(); + return true; } void Peer::_dispatchResponse(uint32_t id, const std::string &name, msgpack::object &res) { - UNIQUE_LOCK(cb_mtx_,lk); - if (callbacks_.count(id) > 0) { - - // Allow for unlock before callback - auto cb = std::move(callbacks_[id]); - callbacks_.erase(id); - lk.unlock(); - - // Call the callback with unpacked return value - try { - (*cb)(res); - } catch(std::exception &e) { - net_->_notifyError(this, ftl::protocol::Error::kRPCResponse, e.what()); - } - } else { - net_->_notifyError(this, ftl::protocol::Error::kRPCResponse, "Missing RPC callback for result - discarding: " + name); - } + UNIQUE_LOCK(cb_mtx_, lk); + if (callbacks_.count(id) > 0) { + // Allow for unlock before callback + auto cb = std::move(callbacks_[id]); + callbacks_.erase(id); + lk.unlock(); + + // Call the callback with unpacked return value + try { + (*cb)(res); + } catch(std::exception &e) { + net_->_notifyError(this, Error::kRPCResponse, e.what()); + } + } else { + net_->_notifyError(this, Error::kRPCResponse, "Missing RPC callback for result - discarding: " + name); + } } void Peer::cancelCall(int id) { - UNIQUE_LOCK(cb_mtx_,lk); - if (callbacks_.count(id) > 0) { - callbacks_.erase(id); - } + UNIQUE_LOCK(cb_mtx_, lk); + if (callbacks_.count(id) > 0) { + callbacks_.erase(id); + } } void Peer::_sendResponse(uint32_t id, const std::string &name, const msgpack::object &res) { - Dispatcher::response_t res_obj = std::make_tuple(1,id,name,res); - UNIQUE_LOCK(send_mtx_,lk); - msgpack::pack(send_buf_, res_obj); - _send(); + Dispatcher::response_t res_obj = std::make_tuple(1, id, name, res); + UNIQUE_LOCK(send_mtx_, lk); + msgpack::pack(send_buf_, res_obj); + _send(); } void Peer::_waitCall(int id, std::condition_variable &cv, bool &hasreturned, const std::string &name) { - std::mutex m; - - int64_t beginat = ftl::time::get_time(); - std::function<void(int)> j; - while (!hasreturned) { - // Attempt to do a thread pool job if available - if ((bool)(j=ftl::pool.pop())) { - j(-1); - } else { - // Block for a little otherwise - std::unique_lock<std::mutex> lk(m); - cv.wait_for(lk, std::chrono::milliseconds(2), [&hasreturned]{return hasreturned;}); - } - - if (ftl::time::get_time() - beginat > 1000) break; - } - - if (!hasreturned) { - cancelCall(id); - throw FTL_Error("RPC failed with timeout: " << name); - } + std::mutex m; + + int64_t beginat = ftl::time::get_time(); + std::function<void(int)> j; + while (!hasreturned) { + // Attempt to do a thread pool job if available + if (static_cast<bool>(j = ftl::pool.pop())) { + j(-1); + } else { + // Block for a little otherwise + std::unique_lock<std::mutex> lk(m); + cv.wait_for(lk, std::chrono::milliseconds(2), [&hasreturned]{return hasreturned;}); + } + + if (ftl::time::get_time() - beginat > 1000) break; + } + + if (!hasreturned) { + cancelCall(id); + throw FTL_Error("RPC failed with timeout: " << name); + } } bool Peer::waitConnection(int s) { - if (status_ == NodeStatus::kConnected) return true; - else if (status_ == NodeStatus::kDisconnected) return false; - - std::mutex m; - m.lock(); - std::condition_variable_any cv; - - auto h = net_->onConnect([this, &cv](const PeerPtr &p) { - if (p.get() == this) { - cv.notify_one(); - } - return true; - }); - - cv.wait_for(m, seconds(s), [this]() { return status_ == NodeStatus::kConnected;}); - m.unlock(); - return status_ == NodeStatus::kConnected; + if (status_ == NodeStatus::kConnected) return true; + else if (status_ == NodeStatus::kDisconnected) return false; + + std::mutex m; + m.lock(); + std::condition_variable_any cv; + + auto h = net_->onConnect([this, &cv](const PeerPtr &p) { + if (p.get() == this) { + cv.notify_one(); + } + return true; + }); + + cv.wait_for(m, seconds(s), [this]() { return status_ == NodeStatus::kConnected;}); + m.unlock(); + return status_ == NodeStatus::kConnected; } int Peer::_send() { - if (!sock_->is_valid()) return -1; - - ssize_t c = 0; - - try { - c = sock_->writev(send_buf_.vector(), send_buf_.vector_size()); - if (c <= 0) { - // writev() should probably throw exception which is reported here - // at the moment, error message is (should be) printed by writev() - net_->_notifyError(this, ftl::protocol::Error::kSocketError, "writev() failed"); - return c; - } - - ssize_t sz = 0; for (size_t i = 0; i < send_buf_.vector_size(); i++) { - sz += send_buf_.vector()[i].iov_len; - } - if (c != sz) { - net_->_notifyError(this, ftl::protocol::Error::kSocketError, "writev(): incomplete send"); - _close(reconnect_on_socket_error_); - } - - send_buf_.clear(); - - } catch (std::exception& ex) { - net_->_notifyError(this, ftl::protocol::Error::kSocketError, ex.what()); - _close(reconnect_on_socket_error_); - } - - return c; + if (!sock_->is_valid()) return -1; + + ssize_t c = 0; + + try { + c = sock_->writev(send_buf_.vector(), send_buf_.vector_size()); + if (c <= 0) { + // writev() should probably throw exception which is reported here + // at the moment, error message is (should be) printed by writev() + net_->_notifyError(this, ftl::protocol::Error::kSocketError, "writev() failed"); + return c; + } + + ssize_t sz = 0; for (size_t i = 0; i < send_buf_.vector_size(); i++) { + sz += send_buf_.vector()[i].iov_len; + } + if (c != sz) { + net_->_notifyError(this, ftl::protocol::Error::kSocketError, "writev(): incomplete send"); + _close(reconnect_on_socket_error_); + } + + send_buf_.clear(); + } catch (std::exception& ex) { + net_->_notifyError(this, ftl::protocol::Error::kSocketError, ex.what()); + _close(reconnect_on_socket_error_); + } + + return c; } Peer::~Peer() { - --net_->peer_instances_; - { - UNIQUE_LOCK(send_mtx_,lk1); - //UNIQUE_LOCK(recv_mtx_,lk2); - _close(false); - } - - // Prevent deletion if there are any jobs remaining - if (job_count_ > 0 && ftl::pool.size() > 0) { - DLOG(1) << "Waiting on peer jobs... " << job_count_; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - if (job_count_ > 0) LOG(FATAL) << "Peer jobs not terminated"; - } + --net_->peer_instances_; + { + UNIQUE_LOCK(send_mtx_, lk1); + // UNIQUE_LOCK(recv_mtx_,lk2); + _close(false); + } + + // Prevent deletion if there are any jobs remaining + if (job_count_ > 0 && ftl::pool.size() > 0) { + DLOG(1) << "Waiting on peer jobs... " << job_count_; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + if (job_count_ > 0) LOG(FATAL) << "Peer jobs not terminated"; + } } diff --git a/src/peer.hpp b/src/peer.hpp index df7bae20238d3a96a5ba77feb5e32a3dc4d030bf..fb1a2ba283ae9f7cbe9d23f89f4201f052c53664 100644 --- a/src/peer.hpp +++ b/src/peer.hpp @@ -10,6 +10,17 @@ #define NOMINMAX #endif +#include <tuple> +#include <vector> +#include <type_traits> +#include <thread> +#include <condition_variable> +#include <chrono> +#include <memory> +#include <map> +#include <utility> +#include <string> + #include <msgpack.hpp> #include "common_fwd.hpp" #include "socket.hpp" @@ -22,14 +33,6 @@ #include <ftl/uuid.hpp> #include <ftl/threads.hpp> -#include <tuple> -#include <vector> -#include <type_traits> -#include <thread> -#include <condition_variable> -#include <chrono> -#include <memory> - # define ENABLE_IF(...) \ typename std::enable_if<(__VA_ARGS__), bool>::type = true @@ -43,14 +46,17 @@ namespace net { class Universe; struct virtual_caller { - virtual void operator()(msgpack::object &o)=0; + virtual void operator()(msgpack::object &o) = 0; }; template <typename T> struct caller : virtual_caller { - explicit caller(const std::function<void(const T&)> &f) : f_(f) {}; - void operator()(msgpack::object &o) override { T r = o.as<T>(); f_(r); }; - std::function<void(const T&)> f_; + explicit caller(const std::function<void(const T&)> &f) : f_(f) {} + void operator()(msgpack::object &o) override { + T r = o.as<T>(); + f_(r); + } + std::function<void(const T&)> f_; }; /** @@ -58,303 +64,306 @@ struct caller : virtual_caller { * created directly. */ class Peer { - public: - friend class Universe; - friend class Dispatcher; - - /** Peer for outgoing connection: resolve address and connect */ - explicit Peer(const ftl::URI& uri, ftl::net::Universe*, ftl::net::Dispatcher* d=nullptr); - - /** Peer for incoming connection: take ownership of given connection */ - explicit Peer(std::unique_ptr<internal::SocketConnection> s, ftl::net::Universe*, ftl::net::Dispatcher* d=nullptr); - - ~Peer(); - - void start(); - - /** - * Close the peer if open. Setting retry parameter to true will initiate - * backoff retry attempts. This is used to deliberately close a connection - * and not for error conditions where different close semantics apply. - * - * @param retry Should reconnection be attempted? - */ - void close(bool retry=false); - - bool isConnected() const; - /** - * Block until the connection and handshake has completed. You should use - * onConnect callbacks instead of blocking, mostly this is intended for - * the unit tests to keep them synchronous. - * - * @return True if all connections were successful, false if timeout or error. - */ - bool waitConnection(int seconds = 1); - - /** - * Make a reconnect attempt. Called internally by Universe object. - */ - bool reconnect(); - - inline bool isOutgoing() const { return outgoing_; } - - /** - * Test if the connection is valid. This returns true in all conditions - * except where the socket has been disconnected permenantly, or was never - * able to connect, perhaps due to an invalid address, or is in middle of a - * reconnect attempt. (Valid states: kConnecting, kConnected) - * - * Should return true only in cases when valid OS socket exists. - */ - bool isValid() const; - - /** peer type */ - ftl::protocol::NodeType getType() const; - - ftl::protocol::NodeStatus status() const { return status_; } - - uint32_t getFTLVersion() const { return version_; } - uint8_t getFTLMajor() const { return version_ >> 16; } - uint8_t getFTLMinor() const { return (version_ >> 8) & 0xFF; } - uint8_t getFTLPatch() const { return version_ & 0xFF; } - - /** - * Get the sockets protocol, address and port as a url string. This will be - * the same as the initial connection string on the client. - */ - std::string getURI() const { return uri_.to_string(); }; - - const ftl::URI &getURIObject() const { return uri_; } - - /** - * Get the UUID for this peer. - */ - const ftl::UUID &id() const { return peerid_; }; - - /** - * Get the peer id as a string. - */ - std::string to_string() const { return peerid_.to_string(); }; - - /** - * Non-blocking Remote Procedure Call using a callback function. - * - * @param name RPC Function name. - * @param cb Completion callback. - * @param args A variable number of arguments for RPC function. - * - * @return A call id for use with cancelCall() if needed. - */ - template <typename T, typename... ARGS> - int asyncCall(const std::string &name, - std::function<void(const T&)> cb, - ARGS... args); - - /** - * Used to terminate an async call if the response is not required. - * - * @param id The ID returned by the original asyncCall request. - */ - void cancelCall(int id); - - /** - * Blocking Remote Procedure Call using a string name. - */ - template <typename R, typename... ARGS> - R call(const std::string &name, ARGS... args); - - /** - * Non-blocking send using RPC function, but with no return value. - * - * @param name RPC Function name - * @param args Variable number of arguments for function - * - * @return Number of bytes sent or -1 if error - */ - template <typename... ARGS> - int send(const std::string &name, ARGS... args); - - template <typename... ARGS> - int try_send(const std::string &name, ARGS... args); - - /** - * Bind a function to an RPC call name. Note: if an overriding dispatcher - * is used then these bindings will propagate to all peers sharing that - * dispatcher. - * - * @param name RPC name to bind to - * @param func A function object to act as callback - */ - template <typename F> - void bind(const std::string &name, F func); - - void rawClose(); - - inline void noReconnect() { can_reconnect_ = false; } - - inline unsigned int localID() const { return local_id_; } - - int connectionCount() const { return connection_count_; } - - /** - * @brief Call recv to get data. Internal use, it is blocking so should only - * be done if data is available. - * - */ - void data(); - - public: - static const int kMaxMessage = 2*1024*1024; // 10Mb currently - -private: // Functions - bool socketError(); // Process one error from socket - void error(int e); - - // check if buffer has enough decoded data from lower layer and advance - // buffer if necessary (skip headers etc). - bool _has_next(); - - // After data is read from network, _data() is called on new thread. - // Received data is kept valid until _data() returns - // (by msgpack::object_handle in local scope). - bool _data(); - - // close socket without sending disconnect message - void _close(bool retry=true); - - void _dispatchResponse(uint32_t id, const std::string &name, msgpack::object &obj); - void _sendResponse(uint32_t id, const std::string &name, const msgpack::object &obj); - - /** - * Get the internal OS dependent socket. - * TODO(nick) Work out if this should be private. Used by select() in - * Universe (universe.cpp) - */ - int _socket() const; - - void _send_handshake(); - void _process_handshake(uint64_t magic, uint32_t version, UUID pid); - - void _updateURI(); - void _set_socket_options(); - void _bind_rpc(); - - void _connect(); - int _send(); - - void _createJob(); - - void _waitCall(int id, std::condition_variable &cv, bool &hasreturned, const std::string &name); - - template<typename... ARGS> - void _trigger(const std::vector<std::function<void(Peer &, ARGS...)>> &hs, ARGS... args) { - for (auto h : hs) { - h(*this, args...); - } - } - - std::atomic_flag already_processing_ = ATOMIC_FLAG_INIT; - std::atomic_flag recv_checked_ = ATOMIC_FLAG_INIT; - - msgpack::unpacker recv_buf_; - MUTEX recv_mtx_; - - // Send buffers - msgpack::vrefbuffer send_buf_; - RECURSIVE_MUTEX send_mtx_; - - RECURSIVE_MUTEX cb_mtx_; - - const bool outgoing_; - unsigned int local_id_; - ftl::URI uri_; // Original connection URI, or assumed URI - ftl::UUID peerid_; // Received in handshake or allocated - ftl::protocol::NodeStatus status_; // Connected, errored, reconnecting.. - uint32_t version_; // Received protocol version in handshake - bool can_reconnect_; // Client connections can retry - ftl::net::Universe *net_; // Origin net universe - - std::unique_ptr<internal::SocketConnection> sock_; - std::unique_ptr<ftl::net::Dispatcher> disp_; // For RPC call dispatch - std::map<int, std::unique_ptr<virtual_caller>> callbacks_; - - std::atomic_int job_count_ = 0; // Ensure threads are done before destructing - std::atomic_int connection_count_ = 0; // Number of successful connections total - std::atomic_int retry_count_ = 0; // Current number of reconnection attempts - - // reconnect when clean disconnect received from remote - bool reconnect_on_remote_disconnect_ = true; - // reconnect on socket error/disconnect without message (remote crash ...) - bool reconnect_on_socket_error_ = true; - // reconnect on protocol error (msgpack decode, bad handshake, ...) - bool reconnect_on_protocol_error_ = false; - - static std::atomic_int rpcid__; // Return ID for RPC calls + public: + friend class Universe; + friend class Dispatcher; + + /** Peer for outgoing connection: resolve address and connect */ + explicit Peer(const ftl::URI& uri, ftl::net::Universe*, ftl::net::Dispatcher* d = nullptr); + + /** Peer for incoming connection: take ownership of given connection */ + explicit Peer( + std::unique_ptr<internal::SocketConnection> s, + ftl::net::Universe*, + ftl::net::Dispatcher* d = nullptr); + + ~Peer(); + + void start(); + + /** + * Close the peer if open. Setting retry parameter to true will initiate + * backoff retry attempts. This is used to deliberately close a connection + * and not for error conditions where different close semantics apply. + * + * @param retry Should reconnection be attempted? + */ + void close(bool retry = false); + + bool isConnected() const; + /** + * Block until the connection and handshake has completed. You should use + * onConnect callbacks instead of blocking, mostly this is intended for + * the unit tests to keep them synchronous. + * + * @return True if all connections were successful, false if timeout or error. + */ + bool waitConnection(int seconds = 1); + + /** + * Make a reconnect attempt. Called internally by Universe object. + */ + bool reconnect(); + + inline bool isOutgoing() const { return outgoing_; } + + /** + * Test if the connection is valid. This returns true in all conditions + * except where the socket has been disconnected permenantly, or was never + * able to connect, perhaps due to an invalid address, or is in middle of a + * reconnect attempt. (Valid states: kConnecting, kConnected) + * + * Should return true only in cases when valid OS socket exists. + */ + bool isValid() const; + + /** peer type */ + ftl::protocol::NodeType getType() const; + + ftl::protocol::NodeStatus status() const { return status_; } + + uint32_t getFTLVersion() const { return version_; } + uint8_t getFTLMajor() const { return version_ >> 16; } + uint8_t getFTLMinor() const { return (version_ >> 8) & 0xFF; } + uint8_t getFTLPatch() const { return version_ & 0xFF; } + + /** + * Get the sockets protocol, address and port as a url string. This will be + * the same as the initial connection string on the client. + */ + std::string getURI() const { return uri_.to_string(); } + + const ftl::URI &getURIObject() const { return uri_; } + + /** + * Get the UUID for this peer. + */ + const ftl::UUID &id() const { return peerid_; } + + /** + * Get the peer id as a string. + */ + std::string to_string() const { return peerid_.to_string(); } + + /** + * Non-blocking Remote Procedure Call using a callback function. + * + * @param name RPC Function name. + * @param cb Completion callback. + * @param args A variable number of arguments for RPC function. + * + * @return A call id for use with cancelCall() if needed. + */ + template <typename T, typename... ARGS> + int asyncCall(const std::string &name, + std::function<void(const T&)> cb, + ARGS... args); + + /** + * Used to terminate an async call if the response is not required. + * + * @param id The ID returned by the original asyncCall request. + */ + void cancelCall(int id); + + /** + * Blocking Remote Procedure Call using a string name. + */ + template <typename R, typename... ARGS> + R call(const std::string &name, ARGS... args); + + /** + * Non-blocking send using RPC function, but with no return value. + * + * @param name RPC Function name + * @param args Variable number of arguments for function + * + * @return Number of bytes sent or -1 if error + */ + template <typename... ARGS> + int send(const std::string &name, ARGS&&... args); + + template <typename... ARGS> + int try_send(const std::string &name, ARGS... args); + + /** + * Bind a function to an RPC call name. Note: if an overriding dispatcher + * is used then these bindings will propagate to all peers sharing that + * dispatcher. + * + * @param name RPC name to bind to + * @param func A function object to act as callback + */ + template <typename F> + void bind(const std::string &name, F func); + + void rawClose(); + + inline void noReconnect() { can_reconnect_ = false; } + + inline unsigned int localID() const { return local_id_; } + + int connectionCount() const { return connection_count_; } + + /** + * @brief Call recv to get data. Internal use, it is blocking so should only + * be done if data is available. + * + */ + void data(); + + public: + static const int kMaxMessage = 2*1024*1024; // 10Mb currently + + private: // Functions + bool socketError(); // Process one error from socket + void error(int e); + + // check if buffer has enough decoded data from lower layer and advance + // buffer if necessary (skip headers etc). + bool _has_next(); + + // After data is read from network, _data() is called on new thread. + // Received data is kept valid until _data() returns + // (by msgpack::object_handle in local scope). + bool _data(); + + // close socket without sending disconnect message + void _close(bool retry = true); + + void _dispatchResponse(uint32_t id, const std::string &name, msgpack::object &obj); + void _sendResponse(uint32_t id, const std::string &name, const msgpack::object &obj); + + /** + * Get the internal OS dependent socket. + * TODO(nick) Work out if this should be private. Used by select() in + * Universe (universe.cpp) + */ + int _socket() const; + + void _send_handshake(); + void _process_handshake(uint64_t magic, uint32_t version, const UUID &pid); + + void _updateURI(); + void _set_socket_options(); + void _bind_rpc(); + + void _connect(); + int _send(); + + void _createJob(); + + void _waitCall(int id, std::condition_variable &cv, bool &hasreturned, const std::string &name); + + template<typename... ARGS> + void _trigger(const std::vector<std::function<void(Peer &, ARGS...)>> &hs, ARGS... args) { + for (auto h : hs) { + h(*this, args...); + } + } + + std::atomic_flag already_processing_ = ATOMIC_FLAG_INIT; + std::atomic_flag recv_checked_ = ATOMIC_FLAG_INIT; + + msgpack::unpacker recv_buf_; + MUTEX recv_mtx_; + + // Send buffers + msgpack::vrefbuffer send_buf_; + RECURSIVE_MUTEX send_mtx_; + + RECURSIVE_MUTEX cb_mtx_; + + const bool outgoing_; + unsigned int local_id_; + ftl::URI uri_; // Original connection URI, or assumed URI + ftl::UUID peerid_; // Received in handshake or allocated + ftl::protocol::NodeStatus status_; // Connected, errored, reconnecting.. + uint32_t version_; // Received protocol version in handshake + bool can_reconnect_; // Client connections can retry + ftl::net::Universe *net_; // Origin net universe + + std::unique_ptr<internal::SocketConnection> sock_; + std::unique_ptr<ftl::net::Dispatcher> disp_; // For RPC call dispatch + std::map<int, std::unique_ptr<virtual_caller>> callbacks_; + + std::atomic_int job_count_ = 0; // Ensure threads are done before destructing + std::atomic_int connection_count_ = 0; // Number of successful connections total + std::atomic_int retry_count_ = 0; // Current number of reconnection attempts + + // reconnect when clean disconnect received from remote + bool reconnect_on_remote_disconnect_ = true; + // reconnect on socket error/disconnect without message (remote crash ...) + bool reconnect_on_socket_error_ = true; + // reconnect on protocol error (msgpack decode, bad handshake, ...) + bool reconnect_on_protocol_error_ = false; + + static std::atomic_int rpcid__; // Return ID for RPC calls }; // --- Inline Template Implementations ----------------------------------------- template <typename... ARGS> -int Peer::send(const std::string &s, ARGS... args) { - UNIQUE_LOCK(send_mtx_, lk); - auto args_obj = std::make_tuple(args...); - auto call_obj = std::make_tuple(0,s,args_obj); - msgpack::pack(send_buf_, call_obj); - int rc = _send(); - return rc; +int Peer::send(const std::string &s, ARGS&&... args) { + UNIQUE_LOCK(send_mtx_, lk); + auto args_obj = std::make_tuple(args...); + auto call_obj = std::make_tuple(0, s, args_obj); + msgpack::pack(send_buf_, call_obj); + int rc = _send(); + return rc; } template <typename F> void Peer::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(), - typename ftl::internal::func_kind_info<F>::has_peer()); + disp_->bind(name, func, + typename ftl::internal::func_kind_info<F>::result_kind(), + typename ftl::internal::func_kind_info<F>::args_kind(), + typename ftl::internal::func_kind_info<F>::has_peer()); } template <typename R, typename... ARGS> R Peer::call(const std::string &name, ARGS... args) { - bool hasreturned = false; - std::condition_variable cv; - - R result; - int id = asyncCall<R>(name, [&](const R &r) { - result = r; - hasreturned = true; - cv.notify_one(); - }, std::forward<ARGS>(args)...); - - _waitCall(id, cv, hasreturned, name); - - return result; + bool hasreturned = false; + std::condition_variable cv; + + R result; + int id = asyncCall<R>(name, [&](const R &r) { + result = r; + hasreturned = true; + cv.notify_one(); + }, std::forward<ARGS>(args)...); + + _waitCall(id, cv, hasreturned, name); + + return result; } template <typename T, typename... ARGS> int Peer::asyncCall( - const std::string &name, - // cppcheck-suppress * - std::function<void(const T&)> cb, - ARGS... args) { - auto args_obj = std::make_tuple(args...); - uint32_t rpcid = 0; - - { - // Could this be the problem???? - UNIQUE_LOCK(cb_mtx_,lk); - // Register the CB - rpcid = rpcid__++; - callbacks_[rpcid] = std::make_unique<caller<T>>(cb); - } - - auto call_obj = std::make_tuple(0,rpcid,name,args_obj); - - UNIQUE_LOCK(send_mtx_,lk); - msgpack::pack(send_buf_, call_obj); - _send(); - return rpcid; + const std::string &name, + // cppcheck-suppress * + std::function<void(const T&)> cb, + ARGS... args) { + auto args_obj = std::make_tuple(args...); + uint32_t rpcid = 0; + + { + // Could this be the problem???? + UNIQUE_LOCK(cb_mtx_, lk); + // Register the CB + rpcid = rpcid__++; + callbacks_[rpcid] = std::make_unique<caller<T>>(cb); + } + + auto call_obj = std::make_tuple(0, rpcid, name, args_obj); + + UNIQUE_LOCK(send_mtx_, lk); + msgpack::pack(send_buf_, call_obj); + _send(); + return rpcid; } using PeerPtr = std::shared_ptr<ftl::net::Peer>; -}; -}; +} // namespace net +} // namespace ftl diff --git a/src/protocol.cpp b/src/protocol.cpp index 2a45913f94a6d88c3bf272628ad204c1002d80b0..012198d2cc9d66e177d33a0d7c48e7d8c6f6320a 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,3 +1,9 @@ +/** + * @file protocol.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol.hpp> #include <ftl/protocol/self.hpp> #include "universe.hpp" @@ -5,7 +11,7 @@ static std::shared_ptr<ftl::net::Universe> universe; -//ctpl::thread_pool ftl::pool(std::thread::hardware_concurrency()*2); +// ctpl::thread_pool ftl::pool(std::thread::hardware_concurrency()*2); ctpl::thread_pool ftl::pool(4); void ftl::protocol::reset() { diff --git a/src/protocol.hpp b/src/protocol.hpp index 3741af2cde53b8555ca2722754d6cc4e13e9fed5..08065448980f26013e51252134b02f973b83dc9c 100644 --- a/src/protocol.hpp +++ b/src/protocol.hpp @@ -8,9 +8,9 @@ #pragma once -#include <ftl/uuid.hpp> #include <ftl/protocol/config.h> #include <tuple> +#include <ftl/uuid.hpp> namespace ftl { namespace net { @@ -19,7 +19,7 @@ typedef std::tuple<uint64_t, uint32_t, ftl::UUID> Handshake; static const uint64_t kMagic = 0x0009340053640912; static const uint32_t kVersion = (FTL_VERSION_MAJOR << 16) + - (FTL_VERSION_MINOR << 8) + FTL_VERSION_PATCH; + (FTL_VERSION_MINOR << 8) + FTL_VERSION_PATCH; -}; -}; +} // namespace net +} // namespace ftl diff --git a/src/protocol/connection.cpp b/src/protocol/connection.cpp index d0e4f41b4ad422f3e0954541d46793f9f17dc9fe..deab1958aa4f68bf131396fa98da98ae76cc449c 100644 --- a/src/protocol/connection.cpp +++ b/src/protocol/connection.cpp @@ -1,241 +1,251 @@ +/** + * @file connection.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + +#include <vector> +#include <algorithm> +#include <string> #include <loguru.hpp> - #include <ftl/exception.hpp> - #include "connection.hpp" -using namespace ftl::net::internal; +using ftl::net::internal::SocketConnection; +using ftl::net::internal::socket_t; +using ftl::net::internal::SocketServer; using ftl::URI; // SocketConnection //////////////////////////////////////////////////////////// SocketConnection::~SocketConnection() { - sock_.close(); + sock_.close(); } socket_t SocketConnection::fd() { return sock_.fd(); } ftl::URI SocketConnection::uri() { - std::string str = host() + ":" + std::to_string(get_port(addr_)); - return ftl::URI(str); + std::string str = host() + ":" + std::to_string(get_port(addr_)); + return ftl::URI(str); } ftl::URI::scheme_t SocketConnection::scheme() const { - return ftl::URI::scheme_t::SCHEME_NONE; + return ftl::URI::scheme_t::SCHEME_NONE; } bool SocketConnection::is_valid() { - return sock_.is_open(); + return sock_.is_open(); } int SocketConnection::is_fatal(int code) { - return sock_.is_fatal(code); + return sock_.is_fatal(code); } void SocketConnection::connect(const SocketAddress &address, int timeout) { - addr_ = address; - int rv = 0; - if (timeout <= 0) rv = sock_.connect(addr_ ); - else rv = sock_.connect(addr_, timeout); - - if (rv != 0) { throw FTL_Error("connect(): " + sock_.get_error_string()); } + addr_ = address; + int rv = 0; + if (timeout <= 0) rv = sock_.connect(addr_ ); + else + rv = sock_.connect(addr_, timeout); + + if (rv != 0) { throw FTL_Error("connect(): " + sock_.get_error_string()); } } ssize_t SocketConnection::recv(char *buffer, size_t len) { - auto recvd = sock_.recv(buffer, len, 0); - if (recvd == 0) { - LOG(3) << "recv(): read size 0"; - return -1; // -1 means close, 0 means retry - } - if (recvd < 0) { - if (!sock_.is_fatal()) return 0; // Retry - throw FTL_Error(sock_.get_error_string()); - } - return recvd; + auto recvd = sock_.recv(buffer, len, 0); + if (recvd == 0) { + LOG(3) << "recv(): read size 0"; + return -1; // -1 means close, 0 means retry + } + if (recvd < 0) { + if (!sock_.is_fatal()) return 0; // Retry + throw FTL_Error(sock_.get_error_string()); + } + return recvd; } ssize_t SocketConnection::send(const char* buffer, size_t len) { - return sock_.send(buffer, len, 0); + return sock_.send(buffer, len, 0); } ssize_t SocketConnection::writev(const struct iovec *iov, int iovcnt) { - ssize_t sent = sock_.writev(iov, iovcnt); - - ssize_t requested = 0; - for (int i = 0; i < iovcnt; i++) { requested += iov[i].iov_len; } - - if (sent < 0) { - LOG(ERROR) << "writev(): " << sock_.get_error_string(); - if (sock_.is_fatal()) { - return sent; - } - sent = 0; - } - if (sent == requested) { return sent; } - - std::vector<struct iovec> iov_local(iovcnt); - auto* iov_ptr = iov_local.data(); - std::copy(iov, iov + iovcnt, iov_ptr); - - ssize_t sent_total = sent; - int writev_calls = 1; - while (sent_total < requested) { - // ssize_t unsigned on Windows? Define as signed and use isntead of long - long iov_len = long(iov_ptr[0].iov_len) - long(sent); - - if (iov_len < 0) { - // buffer was sent, update sent with remaining bytes and - // repeat with next item in iov - sent = -iov_len; - iov_ptr++; iovcnt--; - continue; - } - - iov_ptr[0].iov_base = static_cast<char*>(iov_ptr[0].iov_base) + sent; - iov_ptr[0].iov_len = iov_ptr[0].iov_len - sent; - - sent = sock_.writev(iov_ptr, iovcnt); - writev_calls++; - - if (sent < 0) { - LOG(ERROR) << "writev(): " << sock_.get_error_string(); - if (sock_.is_fatal()) { - return sent; - } - sent = 0; - } - - sent_total += sent; - } - - LOG(2) << "message required " << writev_calls << " writev() calls"; - - if (can_increase_sock_buffer_) { - auto send_buf_size = sock_.get_send_buffer_size(); - auto send_buf_size_new = size_t(sock_.get_send_buffer_size() * 1.5); - - LOG(WARNING) << "Send buffer size " - << (send_buf_size >> 10) << " KiB. " - << "Increasing socket buffer size to " - << (send_buf_size_new >> 10) << "KiB."; - - if (!sock_.set_send_buffer_size(send_buf_size_new)) { - LOG(ERROR) << "could not increase send buffer size, " - << "set_send_buffer_size() failed"; - can_increase_sock_buffer_ = false; - } - else { - send_buf_size = sock_.get_send_buffer_size() ; - bool error = send_buf_size < send_buf_size_new; - LOG_IF(WARNING, error) - << "could not increase send buffer size " - << "(buffer size: " << send_buf_size << ")"; - can_increase_sock_buffer_ &= !error; - } - } - - return requested; + ssize_t sent = sock_.writev(iov, iovcnt); + + ssize_t requested = 0; + for (int i = 0; i < iovcnt; i++) { requested += iov[i].iov_len; } + + if (sent < 0) { + LOG(ERROR) << "writev(): " << sock_.get_error_string(); + if (sock_.is_fatal()) { + return sent; + } + sent = 0; + } + if (sent == requested) { return sent; } + + std::vector<struct iovec> iov_local(iovcnt); + auto* iov_ptr = iov_local.data(); + std::copy(iov, iov + iovcnt, iov_ptr); + + ssize_t sent_total = sent; + int writev_calls = 1; + while (sent_total < requested) { + // ssize_t unsigned on Windows? Define as signed and use isntead of long + int64_t iov_len = int64_t(iov_ptr[0].iov_len) - int64_t(sent); + + if (iov_len < 0) { + // buffer was sent, update sent with remaining bytes and + // repeat with next item in iov + sent = -iov_len; + iov_ptr++; + iovcnt--; + continue; + } + + iov_ptr[0].iov_base = static_cast<char*>(iov_ptr[0].iov_base) + sent; + iov_ptr[0].iov_len = iov_ptr[0].iov_len - sent; + + sent = sock_.writev(iov_ptr, iovcnt); + writev_calls++; + + if (sent < 0) { + LOG(ERROR) << "writev(): " << sock_.get_error_string(); + if (sock_.is_fatal()) { + return sent; + } + sent = 0; + } + + sent_total += sent; + } + + LOG(2) << "message required " << writev_calls << " writev() calls"; + + if (can_increase_sock_buffer_) { + auto send_buf_size = sock_.get_send_buffer_size(); + auto send_buf_size_new = size_t(sock_.get_send_buffer_size() * 1.5); + + LOG(WARNING) << "Send buffer size " + << (send_buf_size >> 10) << " KiB. " + << "Increasing socket buffer size to " + << (send_buf_size_new >> 10) << "KiB."; + + if (!sock_.set_send_buffer_size(send_buf_size_new)) { + LOG(ERROR) << "could not increase send buffer size, " + << "set_send_buffer_size() failed"; + can_increase_sock_buffer_ = false; + } else { + send_buf_size = sock_.get_send_buffer_size(); + bool error = send_buf_size < send_buf_size_new; + LOG_IF(WARNING, error) + << "could not increase send buffer size " + << "(buffer size: " << send_buf_size << ")"; + can_increase_sock_buffer_ &= !error; + } + } + + return requested; } bool SocketConnection::close() { - return sock_.close(); + return sock_.close(); } std::string SocketConnection::host() { - return get_host(addr_); + return get_host(addr_); } int SocketConnection::port() { - LOG(ERROR) << "port() not implemented"; - return -1; + LOG(ERROR) << "port() not implemented"; + return -1; } bool SocketConnection::set_recv_buffer_size(size_t sz) { - auto old = get_recv_buffer_size(); - auto ok = sock_.set_recv_buffer_size(sz); - if (!ok) { - LOG(ERROR) << "setting socket send buffer size failed:" - << sock_.get_error_string(); - } - if (get_recv_buffer_size() == old) { - LOG(ERROR) << "recv buffer size was not changed"; - } - return ok; + auto old = get_recv_buffer_size(); + auto ok = sock_.set_recv_buffer_size(sz); + if (!ok) { + LOG(ERROR) << "setting socket send buffer size failed:" + << sock_.get_error_string(); + } + if (get_recv_buffer_size() == old) { + LOG(ERROR) << "recv buffer size was not changed"; + } + return ok; } bool SocketConnection::set_send_buffer_size(size_t sz) { - auto old = get_send_buffer_size(); - auto ok = sock_.set_send_buffer_size(sz); - if (!ok) { - LOG(ERROR) << "setting socket send buffer size failed:" - << sock_.get_error_string(); - } - if (get_send_buffer_size() == old) { - LOG(ERROR) << "send buffer size was not changed"; - } + auto old = get_send_buffer_size(); + auto ok = sock_.set_send_buffer_size(sz); + if (!ok) { + LOG(ERROR) << "setting socket send buffer size failed:" + << sock_.get_error_string(); + } + if (get_send_buffer_size() == old) { + LOG(ERROR) << "send buffer size was not changed"; + } - return ok; + return ok; } size_t SocketConnection::get_recv_buffer_size() { - return sock_.get_recv_buffer_size(); + return sock_.get_recv_buffer_size(); } size_t SocketConnection::get_send_buffer_size() { - return sock_.get_send_buffer_size(); + return sock_.get_send_buffer_size(); } int SocketConnection::getSocketError() { - int val = 0; - socklen_t optlen = sizeof(val); - if (sock_.getsockopt(SOL_SOCKET, SO_ERROR, &val, &optlen) == 0) { - return val; - } - return errno; // TODO: Windows. + int val = 0; + socklen_t optlen = sizeof(val); + if (sock_.getsockopt(SOL_SOCKET, SO_ERROR, &val, &optlen) == 0) { + return val; + } + return errno; // TODO(Seb): Windows. } // SocketServer //////////////////////////////////////////////////////////////// socket_t SocketServer::fd() { - return sock_.fd(); + return sock_.fd(); } bool SocketServer::is_listening() { - return is_listening_; + return is_listening_; } bool SocketServer::bind(const SocketAddress &address, int backlog) { - bool retval = true; + bool retval = true; - retval &= sock_.bind(address) == 0; - if (!retval) { - auto msg = sock_.get_error_string(); - throw FTL_Error("socket error:" + msg); - } + retval &= sock_.bind(address) == 0; + if (!retval) { + auto msg = sock_.get_error_string(); + throw FTL_Error("socket error:" + msg); + } - retval &= sock_.listen(backlog) == 0; - if (!retval) { - auto msg = sock_.get_error_string(); - throw FTL_Error("socket error:" + msg); - } else { is_listening_ = true; } - - addr_ = sock_.getsockname(); - return retval; + retval &= sock_.listen(backlog) == 0; + if (!retval) { + auto msg = sock_.get_error_string(); + throw FTL_Error("socket error:" + msg); + } else { is_listening_ = true; } + + addr_ = sock_.getsockname(); + return retval; } bool SocketServer::bind(int backlog) { - return bind(addr_, backlog); + return bind(addr_, backlog); } bool SocketServer::close() { - is_listening_ = false; - return sock_.close(); + is_listening_ = false; + return sock_.close(); } std::string SocketServer::host() { - return get_host(addr_); + return get_host(addr_); } int SocketServer::port() { - return get_port(addr_); -} \ No newline at end of file + return get_port(addr_); +} diff --git a/src/protocol/connection.hpp b/src/protocol/connection.hpp index 5b320a63df7f21d68e8e915d9051ecc54bab4318..58d6b78e57606b05997885d8be6ee575c18da3be 100644 --- a/src/protocol/connection.hpp +++ b/src/protocol/connection.hpp @@ -1,11 +1,15 @@ -#ifndef _FTL_NET_SOCKET_CONNECTION_HPP_ -#define _FTL_NET_SOCKET_CONNECTION_HPP_ +/** + * @file connection.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ -#include <memory> +#pragma once +#include <memory> +#include <string> #include <ftl/exception.hpp> #include <ftl/uri.hpp> - #include "../socketImpl.hpp" namespace ftl { @@ -18,65 +22,67 @@ namespace internal { * Assumes IP socket. */ class SocketConnection { -protected: - Socket sock_; - SocketAddress addr_; // move to socket? save uri here + protected: + Socket sock_; + SocketAddress addr_; // move to socket? save uri here + + void connect(const SocketAddress &address, int timeout = 0); - void connect(const SocketAddress &address, int timeout=0); + SocketConnection() {} - SocketConnection() {}; + private: + bool can_increase_sock_buffer_; -private: - bool can_increase_sock_buffer_; + public: + SocketConnection(const SocketConnection&) = delete; -public: - SocketConnection(const SocketConnection&) = delete; + SocketConnection(Socket socket, SocketAddress addr) : + sock_(socket), addr_(addr), can_increase_sock_buffer_(true) {} - SocketConnection(Socket socket, SocketAddress addr) : - sock_(socket), addr_(addr), can_increase_sock_buffer_(true) {}; - - virtual ~SocketConnection(); + virtual ~SocketConnection(); - // connection accepts reads/writes - virtual bool is_valid(); + // connection accepts reads/writes + virtual bool is_valid(); - // OS socket file descriptor - virtual socket_t fd(); + // OS socket file descriptor + virtual socket_t fd(); - virtual ftl::URI uri(); - virtual ftl::URI::scheme_t scheme() const; + virtual ftl::URI uri(); + virtual ftl::URI::scheme_t scheme() const; - virtual void connect(const ftl::URI& uri, int timeout=0) = 0; - - // virtual void connect(int timeout=0); // TODO: set uri in constructor - - // close connection, return true if operation successful. never throws. - virtual bool close(); + virtual void connect(const ftl::URI& uri, int timeout = 0) = 0; - // process next item in buffer, returns true if available and sets offset - // to number of bytes to skip in buffer (variable length headers etc.) - virtual bool prepare_next(char* buffer, size_t len, size_t &offset) - { offset = 0; return true; } + // virtual void connect(int timeout=0); // TODO: set uri in constructor - // send, returns number of bytes sent - virtual ssize_t send(const char* buffer, size_t len); - // receive, returns number of bytes received - virtual ssize_t recv(char *buffer, size_t len); - // scatter write, return number of bytes sent. always sends all data in iov. - virtual ssize_t writev(const struct iovec *iov, int iovcnt); + // close connection, return true if operation successful. never throws. + virtual bool close(); - virtual bool set_recv_buffer_size(size_t sz); - virtual bool set_send_buffer_size(size_t sz); - virtual size_t get_recv_buffer_size(); - virtual size_t get_send_buffer_size(); + // process next item in buffer, returns true if available and sets offset + // to number of bytes to skip in buffer (variable length headers etc.) + virtual bool prepare_next(char* buffer, size_t len, size_t &offset) { + offset = 0; + return true; + } - int getSocketError(); + // send, returns number of bytes sent + virtual ssize_t send(const char* buffer, size_t len); + // receive, returns number of bytes received + virtual ssize_t recv(char *buffer, size_t len); + // scatter write, return number of bytes sent. always sends all data in iov. + virtual ssize_t writev(const struct iovec *iov, int iovcnt); - int is_fatal(int code=0); + virtual bool set_recv_buffer_size(size_t sz); + virtual bool set_send_buffer_size(size_t sz); + virtual size_t get_recv_buffer_size(); + virtual size_t get_send_buffer_size(); - virtual std::string host(); - virtual int port(); + int getSocketError(); + + int is_fatal(int code = 0); + + virtual std::string host(); + virtual int port(); }; /** Socket server, wraps listening sockets. Transport protocols can @@ -87,45 +93,43 @@ public: * Assumes IP socket. */ class SocketServer { -protected: - Socket sock_; - SocketAddress addr_; - bool is_listening_; + protected: + Socket sock_; + SocketAddress addr_; + bool is_listening_; - SocketServer() {} + SocketServer() {} -public: - SocketServer(Socket sock, SocketAddress addr) : - sock_(sock), addr_(addr), is_listening_(false) {}; - - // return OS file descriptor (for select()/poll()/etc.) - socket_t fd(); + public: + SocketServer(Socket sock, SocketAddress addr) : + sock_(sock), addr_(addr), is_listening_(false) {} - virtual bool is_listening(); + // return OS file descriptor (for select()/poll()/etc.) + socket_t fd(); - // bind and listen socket, throws exception on error - virtual bool bind(const SocketAddress &address, int backlog=0); - virtual bool bind(int backlog=0); + virtual bool is_listening(); - // accept connection, throws exception on error - virtual std::unique_ptr<SocketConnection> accept() = 0; + // bind and listen socket, throws exception on error + virtual bool bind(const SocketAddress &address, int backlog = 0); + virtual bool bind(int backlog = 0); - /// stop accepting new connection and close underlying socket - virtual bool close(); + // accept connection, throws exception on error + virtual std::unique_ptr<SocketConnection> accept() = 0; - virtual ftl::URI uri() = 0; + /// stop accepting new connection and close underlying socket + virtual bool close(); - /// avoid use, use URI etc. instead - virtual std::string host(); - /// avoid use, use URI etc. instead - virtual int port(); + virtual ftl::URI uri() = 0; + + /// avoid use, use URI etc. instead + virtual std::string host(); + /// avoid use, use URI etc. instead + virtual int port(); }; // SocketConnection factory std::unique_ptr<SocketConnection> createConnection(const ftl::URI &uri); -} // namespace internal -} // namespace net -} // namespace ftl - -#endif \ No newline at end of file +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/protocol/factory.cpp b/src/protocol/factory.cpp index e966edb619401b46a42109d03510440705e74395..573a299f8e8590e67874afad5fb36b3278455b6b 100644 --- a/src/protocol/factory.cpp +++ b/src/protocol/factory.cpp @@ -1,8 +1,12 @@ -#include <loguru.hpp> +/** + * @file factory.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ -#include <ftl/exception.hpp> #include <ftl/protocol/config.h> - +#include <loguru.hpp> +#include <ftl/exception.hpp> #include "connection.hpp" #include "tcp.hpp" #include "tls.hpp" @@ -19,26 +23,26 @@ using ftl::net::internal::Connection_WSS; using ftl::URI; std::unique_ptr<SocketConnection> ftl::net::internal::createConnection(const URI &uri) { - if (uri.getProtocol() == URI::SCHEME_TCP) { - auto c = std::make_unique<Connection_TCP>(); - return c; + if (uri.getProtocol() == URI::SCHEME_TCP) { + auto c = std::make_unique<Connection_TCP>(); + return c; - } else if (uri.getProtocol() == URI::SCHEME_WS) { - auto c = std::make_unique<Connection_WS>(); - return c; + } else if (uri.getProtocol() == URI::SCHEME_WS) { + auto c = std::make_unique<Connection_WS>(); + return c; - } else if (uri.getProtocol() == URI::SCHEME_WSS) { + } else if (uri.getProtocol() == URI::SCHEME_WSS) { #ifdef HAVE_GNUTLS - auto c = std::make_unique<Connection_WSS>(); - return c; + auto c = std::make_unique<Connection_WSS>(); + return c; #else - throw FTL_Error("built without TLS support"); + throw FTL_Error("built without TLS support"); #endif - } else { - //LOG(ERROR) << "can't connect to: " << uri.to_string(); - throw FTL_Error("unrecognised connection protocol: " << uri.to_string()); - } + } else { + // LOG(ERROR) << "can't connect to: " << uri.to_string(); + throw FTL_Error("unrecognised connection protocol: " << uri.to_string()); + } - return nullptr; -} \ No newline at end of file + return nullptr; +} diff --git a/src/protocol/tcp.cpp b/src/protocol/tcp.cpp index e912bf729201f3474753008c81ae0659bdbd7fe3..06460f7c26c185d1ab2de4ab07faec94076e75a5 100644 --- a/src/protocol/tcp.cpp +++ b/src/protocol/tcp.cpp @@ -1,67 +1,76 @@ +/** + * @file tcp.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ - +#include <string> +#include <memory> #include <ftl/exception.hpp> #include "tcp.hpp" #include <ftl/lib/loguru.hpp> -using namespace ftl::net::internal; +using ftl::net::internal::Connection_TCP; +using ftl::net::internal::SocketConnection; +using ftl::net::internal::Server_TCP; +using ftl::net::internal::SocketAddress; +using ftl::net::internal::Socket; // Connection_TCP ////////////////////////////////////////////////////////////// Connection_TCP::Connection_TCP(Socket sock, SocketAddress addr) : SocketConnection(sock, addr) { - if (!sock_.set_nodelay(true) || !sock_.get_nodelay()) { - LOG(ERROR) << "Could not set TCP_NODELAY"; - } + if (!sock_.set_nodelay(true) || !sock_.get_nodelay()) { + LOG(ERROR) << "Could not set TCP_NODELAY"; + } } Connection_TCP::Connection_TCP() : SocketConnection(create_tcp_socket(), {}) { - if (!sock_.set_nodelay(true) || !sock_.get_nodelay()) { - LOG(ERROR) << "Could not set TCP_NODELAY"; - } + if (!sock_.set_nodelay(true) || !sock_.get_nodelay()) { + LOG(ERROR) << "Could not set TCP_NODELAY"; + } } bool Connection_TCP::connect(const std::string &hostname, int port, int timeout) { - if (!resolve_inet_address(hostname, port, addr_)) { - throw FTL_Error("could not resolve hostname: " + hostname); - } - auto err = sock_.connect(addr_); - if (err < 0) { - throw FTL_Error("connect() error: " + sock_.get_error_string()); - } + if (!resolve_inet_address(hostname, port, addr_)) { + throw FTL_Error("could not resolve hostname: " + hostname); + } + auto err = sock_.connect(addr_); + if (err < 0) { + throw FTL_Error("connect() error: " + sock_.get_error_string()); + } - return true; + return true; } void Connection_TCP::connect(const ftl::URI& uri, int timeout) { - if (!connect(uri.getHost(), uri.getPort(), timeout)) { - throw FTL_Error("Could not open TCP connection"); - } + if (!connect(uri.getHost(), uri.getPort(), timeout)) { + throw FTL_Error("Could not open TCP connection"); + } } // Server_TCP ////////////////////////////////////////////////////////////////// Server_TCP::Server_TCP(const std::string &hostname, int port) : - SocketServer(create_tcp_socket(), {}), host_(hostname) { - - if (!resolve_inet_address(hostname, port, addr_)) { - throw FTL_Error("could not resolve " + hostname); - } + SocketServer(create_tcp_socket(), {}), host_(hostname) { + if (!resolve_inet_address(hostname, port, addr_)) { + throw FTL_Error("could not resolve " + hostname); + } - int enable = 1; - if (sock_.setsockopt(SOL_SOCKET, SO_REUSEADDR, (char*)(&enable), sizeof(int)) < 0) { - LOG(ERROR) << "Setting SO_REUSEADDR failed"; - } + int enable = 1; + if (sock_.setsockopt(SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&enable), sizeof(int)) < 0) { + LOG(ERROR) << "Setting SO_REUSEADDR failed"; + } } std::unique_ptr<SocketConnection> Server_TCP::accept() { - SocketAddress addr; - auto sock = sock_.accept(addr); - auto connection = std::unique_ptr<Connection_TCP>( - new Connection_TCP(sock, addr)); // throws on error - return connection; + SocketAddress addr; + auto sock = sock_.accept(addr); + auto connection = std::unique_ptr<Connection_TCP>( + new Connection_TCP(sock, addr)); // throws on error + return connection; } ftl::URI Server_TCP::uri() { - return ftl::URI("tcp://" + host() + ":" + std::to_string(port())); -} \ No newline at end of file + return ftl::URI("tcp://" + host() + ":" + std::to_string(port())); +} diff --git a/src/protocol/tcp.hpp b/src/protocol/tcp.hpp index 5e0fc56b4672dcb84a1b02d1bf536f5345fcdd79..ce9b9278c57cdffd4a647c9589de2fec58be8c32 100644 --- a/src/protocol/tcp.hpp +++ b/src/protocol/tcp.hpp @@ -1,6 +1,13 @@ -#ifndef _FTL_NET_SOCKET_CONNECTION_TCP_HPP_ -#define _FTL_NET_SOCKET_CONNECTION_TCP_HPP_ +/** + * @file tcp.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ +#pragma once + +#include <string> +#include <memory> #include <ftl/exception.hpp> #include <ftl/uri.hpp> @@ -14,36 +21,34 @@ namespace internal { // TCP client/server (directly uses socket without additional processing) class Server_TCP : public SocketServer { -private: - std::string host_; + private: + std::string host_; -protected: - using SocketServer::SocketServer; + protected: + using SocketServer::SocketServer; -public: - Server_TCP(const std::string& hostname, int port); - std::unique_ptr<SocketConnection> accept() override; + public: + Server_TCP(const std::string& hostname, int port); + std::unique_ptr<SocketConnection> accept() override; - ftl::URI uri() override; + ftl::URI uri() override; }; class Connection_TCP : public SocketConnection { -private: - friend class Server_TCP; - -protected: - Connection_TCP(Socket sock, SocketAddress addr); - -public: - Connection_TCP(); - - ftl::URI::scheme_t scheme() const override { return ftl::URI::SCHEME_TCP; } - bool connect(const std::string &hostname, int port, int timeout=0); - void connect(const ftl::URI& uri, int timeout=0) override; -}; + private: + friend class Server_TCP; + + protected: + Connection_TCP(Socket sock, SocketAddress addr); -} // namespace internal -} // namespace net -} // namespace ftl + public: + Connection_TCP(); + + ftl::URI::scheme_t scheme() const override { return ftl::URI::SCHEME_TCP; } + bool connect(const std::string &hostname, int port, int timeout = 0); + void connect(const ftl::URI& uri, int timeout = 0) override; +}; -#endif \ No newline at end of file +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/protocol/tls.cpp b/src/protocol/tls.cpp index 42175003da135674c92aeb763c1677d812f7bde7..cbc17386ec2d67b68b752c0cfaeae2b53c82a1e3 100644 --- a/src/protocol/tls.cpp +++ b/src/protocol/tls.cpp @@ -1,9 +1,16 @@ +/** + * @file tls.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + #include "tls.hpp" #ifdef HAVE_GNUTLS #include <sstream> #include <iomanip> +#include <string> #include <ftl/exception.hpp> #include <ftl/lib/loguru.hpp> @@ -14,126 +21,125 @@ using uchar = unsigned char; /** get basic certificate info: Distinguished Name (DN), issuer DN, * certificate fingerprint */ std::string get_cert_info(gnutls_session_t session) { - const gnutls_datum_t *cert_list; - unsigned int cert_list_size = 0; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size = 0; + + cert_list = gnutls_certificate_get_peers(session, &cert_list_size); - cert_list = gnutls_certificate_get_peers(session, &cert_list_size); + std::string str = ""; - std::string str = ""; + if (cert_list_size > 0) { + gnutls_x509_crt_t cert; + char data[256]; + size_t size; - if (cert_list_size > 0) { - gnutls_x509_crt_t cert; - char data[256]; - size_t size; + gnutls_x509_crt_init(&cert); - gnutls_x509_crt_init(&cert); + gnutls_x509_crt_import(cert, &cert_list[0], + GNUTLS_X509_FMT_DER); - gnutls_x509_crt_import(cert, &cert_list[0], - GNUTLS_X509_FMT_DER); + size = sizeof(data); + gnutls_x509_crt_get_dn(cert, data, &size); + str += "DN: " + std::string(data); - size = sizeof(data); - gnutls_x509_crt_get_dn(cert, data, &size); - str += "DN: " + std::string(data); + size = sizeof(data); + gnutls_x509_crt_get_issuer_dn(cert, data, &size); + str += "; Issuer DN: " + std::string(data); - size = sizeof(data); - gnutls_x509_crt_get_issuer_dn(cert, data, &size); - str += "; Issuer DN: " + std::string(data); + size = sizeof(data); + gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, data, &size); + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (size_t i = 0; i < size; i++) { + ss << std::setw(2) << int((reinterpret_cast<uchar*>(data))[i]); + if (i != (size - 1)) ss << ":"; + } + str += "; certificate fingerprint (SHA1): " + ss.str(); - size = sizeof(data); - gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, data, &size); - std::stringstream ss; - ss << std::hex << std::setfill('0'); - for (size_t i = 0; i < size; i++) { - ss << std::setw(2) << int(((uchar*) data)[i]); - if (i != (size - 1)) ss << ":"; - } - str += "; certificate fingerprint (SHA1): " + ss.str(); + gnutls_x509_crt_deinit(cert); + } - gnutls_x509_crt_deinit(cert); - } - - return str; + return str; } int Connection_TLS::check_gnutls_error_(int errcode) { - if (errcode < 0 && gnutls_error_is_fatal(errcode) == 0) { - Connection_TCP::close(); - } - - if (errcode < 0) { - auto msg = gnutls_strerror(errcode); - throw FTL_Error(msg); - } - - return errcode; -}; + if (errcode < 0 && gnutls_error_is_fatal(errcode) == 0) { + Connection_TCP::close(); + } + + if (errcode < 0) { + auto msg = gnutls_strerror(errcode); + throw FTL_Error(msg); + } + + return errcode; +} bool Connection_TLS::connect(const std::string& hostname, int port, int timeout) { - // TODO: throw if already connected + // TODO(Seb): throw if already connected - check_gnutls_error_(gnutls_certificate_allocate_credentials(&xcred_)); - check_gnutls_error_(gnutls_certificate_set_x509_system_trust(xcred_)); - check_gnutls_error_(gnutls_init(&session_, GNUTLS_CLIENT)); - check_gnutls_error_(gnutls_server_name_set(session_, GNUTLS_NAME_DNS, hostname.c_str(), hostname.length())); + check_gnutls_error_(gnutls_certificate_allocate_credentials(&xcred_)); + check_gnutls_error_(gnutls_certificate_set_x509_system_trust(xcred_)); + check_gnutls_error_(gnutls_init(&session_, GNUTLS_CLIENT)); + check_gnutls_error_(gnutls_server_name_set(session_, GNUTLS_NAME_DNS, hostname.c_str(), hostname.length())); - gnutls_session_set_verify_cert(session_, hostname.c_str(), 0); - check_gnutls_error_(gnutls_set_default_priority(session_)); + gnutls_session_set_verify_cert(session_, hostname.c_str(), 0); + check_gnutls_error_(gnutls_set_default_priority(session_)); - if (!Connection_TCP::connect(hostname, port, timeout)) { - throw FTL_Error("TLS connection failed"); - } + if (!Connection_TCP::connect(hostname, port, timeout)) { + throw FTL_Error("TLS connection failed"); + } - check_gnutls_error_(gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, xcred_)); + check_gnutls_error_(gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, xcred_)); - gnutls_transport_set_int(session_, sock_.fd()); - gnutls_handshake_set_timeout(session_, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + gnutls_transport_set_int(session_, sock_.fd()); + gnutls_handshake_set_timeout(session_, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - check_gnutls_error_(gnutls_handshake(session_)); + check_gnutls_error_(gnutls_handshake(session_)); - LOG(INFO) << "TLS connection established: " - << gnutls_session_get_desc(session_) << "; " - << get_cert_info(session_); + LOG(INFO) << "TLS connection established: " + << gnutls_session_get_desc(session_) << "; " + << get_cert_info(session_); - // try a few times? (included in gnutls example) - // do { ... } while (retval < 0 && gnutls_error_is_fatal(retval) == 0); + // try a few times? (included in gnutls example) + // do { ... } while (retval < 0 && gnutls_error_is_fatal(retval) == 0); - return true; + return true; } bool Connection_TLS::close() { - if (sock_.is_open()) { - gnutls_bye(session_, GNUTLS_SHUT_RDWR); - } - return Connection_TCP::close(); + if (sock_.is_open()) { + gnutls_bye(session_, GNUTLS_SHUT_RDWR); + } + return Connection_TCP::close(); } ssize_t Connection_TLS::recv(char *buffer, size_t len) { - auto recvd = gnutls_record_recv(session_, buffer, len); - if (recvd == 0) { - LOG(1) << "recv returned 0 (buffer size " << len << "), closing connection"; - close(); - } + auto recvd = gnutls_record_recv(session_, buffer, len); + if (recvd == 0) { + LOG(1) << "recv returned 0 (buffer size " << len << "), closing connection"; + close(); + } - return check_gnutls_error_(recvd); + return check_gnutls_error_(recvd); } ssize_t Connection_TLS::send(const char* buffer, size_t len) { - return check_gnutls_error_(gnutls_record_send(session_, buffer, len)); + return check_gnutls_error_(gnutls_record_send(session_, buffer, len)); } ssize_t Connection_TLS::writev(const struct iovec *iov, int iovcnt) { - gnutls_record_cork(session_); - - for (int i = 0; i < iovcnt; i++) { - size_t sent = 0; - do { - // should always succeed and cache whole buffer (no error checking) - sent += send((const char*) iov[i].iov_base, iov[i].iov_len); - } - while(sent < iov[i].iov_len); - } - - return check_gnutls_error_(gnutls_record_uncork(session_, GNUTLS_RECORD_WAIT)); + gnutls_record_cork(session_); + + for (int i = 0; i < iovcnt; i++) { + size_t sent = 0; + do { + // should always succeed and cache whole buffer (no error checking) + sent += send((const char*) iov[i].iov_base, iov[i].iov_len); + } while (sent < iov[i].iov_len); + } + + return check_gnutls_error_(gnutls_record_uncork(session_, GNUTLS_RECORD_WAIT)); } -#endif \ No newline at end of file +#endif diff --git a/src/protocol/tls.hpp b/src/protocol/tls.hpp index 65cad60cdfcd47746616a1c96186b66a515b072b..ca206301b3a4c16379d262e09b96ab09c9a9bbeb 100644 --- a/src/protocol/tls.hpp +++ b/src/protocol/tls.hpp @@ -1,6 +1,13 @@ +/** + * @file tls.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + #pragma once #include <ftl/protocol/config.h> +#include <string> #ifdef HAVE_GNUTLS @@ -19,29 +26,29 @@ namespace net { namespace internal { class Connection_TLS : public Connection_TCP { -public: - Connection_TLS() {}; - Connection_TLS(const std::string &hostname, int port, int timeout=0); - - bool connect(const std::string &hostname, int port, int timeout=0); - - bool close() override; - -protected: - ssize_t recv(char *buffer, size_t len) override; - ssize_t send(const char* buffer, size_t len) override; - ssize_t writev(const struct iovec *iov, int iovcnt) override; - - int check_gnutls_error_(int errcode); // check for fatal error and throw - -private: - gnutls_session_t session_; - gnutls_certificate_credentials_t xcred_; + public: + Connection_TLS() {} + Connection_TLS(const std::string &hostname, int port, int timeout = 0); + + bool connect(const std::string &hostname, int port, int timeout = 0); + + bool close() override; + + protected: + ssize_t recv(char *buffer, size_t len) override; + ssize_t send(const char* buffer, size_t len) override; + ssize_t writev(const struct iovec *iov, int iovcnt) override; + + int check_gnutls_error_(int errcode); // check for fatal error and throw + + private: + gnutls_session_t session_; + gnutls_certificate_credentials_t xcred_; }; -} -} -} +} // namespace internal +} // namespace net +} // namespace ftl -#endif // HAVE_GNUTLS +#endif // HAVE_GNUTLS diff --git a/src/protocol/websocket.cpp b/src/protocol/websocket.cpp index 282c471a81eaebdfc9304c1d86b8a286e69886a9..b1c64ea45cbdec1aaaf4bb4b98b65f49a60935fe 100644 --- a/src/protocol/websocket.cpp +++ b/src/protocol/websocket.cpp @@ -1,3 +1,11 @@ +/** + * @file websocket.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + +#include <string> +#include <algorithm> #include "websocket.hpp" #include <ftl/lib/loguru.hpp> @@ -9,9 +17,9 @@ using uchar = unsigned char; #include <gnutls/crypto.h> inline uint32_t secure_rnd() { - uint32_t rnd; - gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(uint32_t)); - return rnd; + uint32_t rnd; + gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(uint32_t)); + return rnd; } #else #include <random> @@ -19,323 +27,321 @@ static std::random_device rd_; static std::uniform_int_distribution<uint32_t> dist_(0); inline uint32_t secure_rnd() { - // TODO - return dist_(rd_); + // TODO(Seb) + return dist_(rd_); } #endif using ftl::URI; -using namespace ftl::net::internal; +using ftl::net::internal::WebSocketBase; +using ftl::net::internal::Connection_TCP; +using ftl::net::internal::Connection_TLS; /* Taken from easywsclient */ struct wsheader_type { - unsigned header_size; - bool fin; - bool mask; - enum opcode_type { - CONTINUATION = 0x0, - TEXT_FRAME = 0x1, - BINARY_FRAME = 0x2, - CLOSE = 8, - PING = 9, - PONG = 0xa, - } opcode; - int N0; - uint64_t N; - uint8_t masking_key[4]; + unsigned header_size; + bool fin; + bool mask; + enum opcode_type { + CONTINUATION = 0x0, + TEXT_FRAME = 0x1, + BINARY_FRAME = 0x2, + CLOSE = 8, + PING = 9, + PONG = 0xa, + } opcode; + int N0; + uint64_t N; + uint8_t masking_key[4]; }; struct ws_options { - std::string userinfo = ""; + std::string userinfo = ""; }; // prepare ws header int ws_prepare(wsheader_type::opcode_type op, bool useMask, uint32_t mask, - size_t len, char *data, size_t maxlen) { - - uint8_t* masking_key = reinterpret_cast<uint8_t*>(&mask); - - char *header = data; - size_t header_size = 2 + (len >= 126 ? 2 : 0) + (len >= 65536 ? 6 : 0) + (useMask ? 4 : 0); - if (header_size > maxlen) return -1; - - memset(header, 0, header_size); - header[0] = 0x80 | op; - - if (len < 126) { - header[1] = (len & 0xff) | (useMask ? 0x80 : 0); - if (useMask) { - header[2] = masking_key[0]; - header[3] = masking_key[1]; - header[4] = masking_key[2]; - header[5] = masking_key[3]; - } - } else if (len < 65536) { - header[1] = 126 | (useMask ? 0x80 : 0); - header[2] = (len >> 8) & 0xff; - header[3] = (len >> 0) & 0xff; - if (useMask) { - header[4] = masking_key[0]; - header[5] = masking_key[1]; - header[6] = masking_key[2]; - header[7] = masking_key[3]; - } - } else { - header[1] = 127 | (useMask ? 0x80 : 0); - header[2] = (len >> 56) & 0xff; - header[3] = (len >> 48) & 0xff; - header[4] = (len >> 40) & 0xff; - header[5] = (len >> 32) & 0xff; - header[6] = (len >> 24) & 0xff; - header[7] = (len >> 16) & 0xff; - header[8] = (len >> 8) & 0xff; - header[9] = (len >> 0) & 0xff; - if (useMask) { - header[10] = masking_key[0]; - header[11] = masking_key[1]; - header[12] = masking_key[2]; - header[13] = masking_key[3]; - } - } - - return int(header_size); + size_t len, char *data, size_t maxlen) { + uint8_t* masking_key = reinterpret_cast<uint8_t*>(&mask); + + char *header = data; + size_t header_size = 2 + (len >= 126 ? 2 : 0) + (len >= 65536 ? 6 : 0) + (useMask ? 4 : 0); + if (header_size > maxlen) return -1; + + memset(header, 0, header_size); + header[0] = 0x80 | op; + + if (len < 126) { + header[1] = (len & 0xff) | (useMask ? 0x80 : 0); + if (useMask) { + header[2] = masking_key[0]; + header[3] = masking_key[1]; + header[4] = masking_key[2]; + header[5] = masking_key[3]; + } + } else if (len < 65536) { + header[1] = 126 | (useMask ? 0x80 : 0); + header[2] = (len >> 8) & 0xff; + header[3] = (len >> 0) & 0xff; + if (useMask) { + header[4] = masking_key[0]; + header[5] = masking_key[1]; + header[6] = masking_key[2]; + header[7] = masking_key[3]; + } + } else { + header[1] = 127 | (useMask ? 0x80 : 0); + header[2] = (len >> 56) & 0xff; + header[3] = (len >> 48) & 0xff; + header[4] = (len >> 40) & 0xff; + header[5] = (len >> 32) & 0xff; + header[6] = (len >> 24) & 0xff; + header[7] = (len >> 16) & 0xff; + header[8] = (len >> 8) & 0xff; + header[9] = (len >> 0) & 0xff; + if (useMask) { + header[10] = masking_key[0]; + header[11] = masking_key[1]; + header[12] = masking_key[2]; + header[13] = masking_key[3]; + } + } + + return static_cast<int>(header_size); } // parse ws header, returns true on success -// TODO: return error code for different results (not enough bytes in buffer -// to build the header vs corrputed/invalid header) -bool ws_parse(uchar *data, size_t len, wsheader_type& ws) { - if (len < 2) return false; - - ws.fin = (data[0] & 0x80) == 0x80; - ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f); - ws.mask = (data[1] & 0x80) == 0x80; - ws.N0 = (data[1] & 0x7f); - ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); - - if (len < ws.header_size) return false; - - // invalid opcode, corrupted header? - if ((ws.opcode > 10) || ((ws.opcode > 2) && (ws.opcode < 8))) return false; - - int i = 0; - if (ws.N0 < 126) { - ws.N = ws.N0; - i = 2; - } else if (ws.N0 == 126) { - ws.N = 0; - ws.N |= ((uint64_t) data[2]) << 8; - ws.N |= ((uint64_t) data[3]) << 0; - i = 4; - } else if (ws.N0 == 127) { - ws.N = 0; - ws.N |= ((uint64_t) data[2]) << 56; - ws.N |= ((uint64_t) data[3]) << 48; - ws.N |= ((uint64_t) data[4]) << 40; - ws.N |= ((uint64_t) data[5]) << 32; - ws.N |= ((uint64_t) data[6]) << 24; - ws.N |= ((uint64_t) data[7]) << 16; - ws.N |= ((uint64_t) data[8]) << 8; - ws.N |= ((uint64_t) data[9]) << 0; - i = 10; - } - - if (ws.mask) { - ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; - ws.masking_key[1] = ((uint8_t) data[i+1]) << 0; - ws.masking_key[2] = ((uint8_t) data[i+2]) << 0; - ws.masking_key[3] = ((uint8_t) data[i+3]) << 0; - } else { - ws.masking_key[0] = 0; - ws.masking_key[1] = 0; - ws.masking_key[2] = 0; - ws.masking_key[3] = 0; - } - - return true; +// TODO(Seb): return error code for different results (not enough bytes in buffer +// to build the header vs corrputed/invalid header) +bool ws_parse(uchar *data, size_t len, wsheader_type *ws) { + if (len < 2) return false; + + ws->fin = (data[0] & 0x80) == 0x80; + ws->opcode = (wsheader_type::opcode_type) (data[0] & 0x0f); + ws->mask = (data[1] & 0x80) == 0x80; + ws->N0 = (data[1] & 0x7f); + ws->header_size = 2 + (ws->N0 == 126? 2 : 0) + (ws->N0 == 127? 8 : 0) + (ws->mask? 4 : 0); + + if (len < ws->header_size) return false; + + // invalid opcode, corrupted header? + if ((ws->opcode > 10) || ((ws->opcode > 2) && (ws->opcode < 8))) return false; + + int i = 0; + if (ws->N0 < 126) { + ws->N = ws->N0; + i = 2; + } else if (ws->N0 == 126) { + ws->N = 0; + ws->N |= ((uint64_t) data[2]) << 8; + ws->N |= ((uint64_t) data[3]) << 0; + i = 4; + } else if (ws->N0 == 127) { + ws->N = 0; + ws->N |= ((uint64_t) data[2]) << 56; + ws->N |= ((uint64_t) data[3]) << 48; + ws->N |= ((uint64_t) data[4]) << 40; + ws->N |= ((uint64_t) data[5]) << 32; + ws->N |= ((uint64_t) data[6]) << 24; + ws->N |= ((uint64_t) data[7]) << 16; + ws->N |= ((uint64_t) data[8]) << 8; + ws->N |= ((uint64_t) data[9]) << 0; + i = 10; + } + + if (ws->mask) { + ws->masking_key[0] = ((uint8_t) data[i+0]) << 0; + ws->masking_key[1] = ((uint8_t) data[i+1]) << 0; + ws->masking_key[2] = ((uint8_t) data[i+2]) << 0; + ws->masking_key[3] = ((uint8_t) data[i+3]) << 0; + } else { + ws->masking_key[0] = 0; + ws->masking_key[1] = 0; + ws->masking_key[2] = 0; + ws->masking_key[3] = 0; + } + + return true; } // same as above, pointer type casted to unsigned -bool ws_parse(char *data, size_t len, wsheader_type& ws) { - return ws_parse(reinterpret_cast<unsigned char*>(data), len, ws); +bool ws_parse(char *data, size_t len, wsheader_type *ws) { + return ws_parse(reinterpret_cast<unsigned char*>(data), len, ws); } int getPort(const ftl::URI &uri) { - auto port = uri.getPort(); - - if (port == 0) { - if (uri.getScheme() == URI::scheme_t::SCHEME_WS) { - port = 80; - } else if (uri.getScheme() == URI::scheme_t::SCHEME_WSS) { - port = 443; - } else { - throw FTL_Error("Bad WS uri:" + uri.to_string()); - } - } - - return port; + auto port = uri.getPort(); + + if (port == 0) { + if (uri.getScheme() == URI::scheme_t::SCHEME_WS) { + port = 80; + } else if (uri.getScheme() == URI::scheme_t::SCHEME_WSS) { + port = 443; + } else { + throw FTL_Error("Bad WS uri:" + uri.to_string()); + } + } + + return port; } //////////////////////////////////////////////////////////////////////////////// template<typename SocketT> -WebSocketBase<SocketT>::WebSocketBase() { - -} +WebSocketBase<SocketT>::WebSocketBase() {} template<typename SocketT> void WebSocketBase<SocketT>::connect(const ftl::URI& uri, int timeout) { - int port = getPort(uri); - - // connect via TCP/TLS - if (!SocketT::connect(uri.getHost(), port, timeout)) { - throw FTL_Error("WS: connect() failed"); - } - - std::string http = ""; - int status; - int i; - char line[256]; - - http += "GET " + uri.getPath() + " HTTP/1.1\r\n"; - if (port == 80) { - http += "Host: " + uri.getHost() + "\r\n"; - } else { - // TODO: is this correct when connecting over TLS - http += "Host: " + uri.getHost() + ":" - + std::to_string(port) + "\r\n"; - } - - if (uri.hasUserInfo()) { - http += "Authorization: Basic "; - http += base64_encode(uri.getUserInfo()) + "\r\n"; - //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization - if (uri.getProtocol() != URI::scheme_t::SCHEME_WSS) { - LOG(WARNING) << "HTTP Basic Auth is being sent without TLS"; - } - } - - http += "Upgrade: websocket\r\n"; - http += "Connection: Upgrade\r\n"; - http += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"; - http += "Sec-WebSocket-Version: 13\r\n"; - http += "\r\n"; - // TODO: check/process HTTP response code - - int rc = SocketT::send(http.c_str(), int(http.length())); - if (rc != (int)http.length()) { - throw FTL_Error("Could not send Websocket http request... (" - + std::to_string(rc) + ", " - + std::to_string(errno) + ")\n" + http); - } - - for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { - if (SocketT::recv(line + i, 1) == 0) { - throw FTL_Error("Connection closed by remote"); - } - } - - line[i] = 0; - if (i == 255) { - throw FTL_Error("Got invalid status line connecting to: " + uri.getHost()); - } - if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { - throw FTL_Error("ERROR: Got bad status connecting to: " - + uri.getHost() + ": " + line); - } - - // TODO: verify response headers, - while (true) { - for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { - if (SocketT::recv(line+i, 1) == 0) { - throw FTL_Error("Connection closed by remote"); - } - } - if (line[0] == '\r' && line[1] == '\n') { break; } - } + int port = getPort(uri); + + // connect via TCP/TLS + if (!SocketT::connect(uri.getHost(), port, timeout)) { + throw FTL_Error("WS: connect() failed"); + } + + std::string http = ""; + int status; + int i; + char line[256]; + + http += "GET " + uri.getPath() + " HTTP/1.1\r\n"; + if (port == 80) { + http += "Host: " + uri.getHost() + "\r\n"; + } else { + // TODO(Seb): is this correct when connecting over TLS + http += "Host: " + uri.getHost() + ":" + + std::to_string(port) + "\r\n"; + } + + if (uri.hasUserInfo()) { + http += "Authorization: Basic "; + http += base64_encode(uri.getUserInfo()) + "\r\n"; + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization + if (uri.getProtocol() != URI::scheme_t::SCHEME_WSS) { + LOG(WARNING) << "HTTP Basic Auth is being sent without TLS"; + } + } + + http += "Upgrade: websocket\r\n"; + http += "Connection: Upgrade\r\n"; + http += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"; + http += "Sec-WebSocket-Version: 13\r\n"; + http += "\r\n"; + // TODO(Seb): check/process HTTP response code + + int rc = SocketT::send(http.c_str(), static_cast<int>(http.length())); + if (rc != static_cast<int>(http.length())) { + throw FTL_Error("Could not send Websocket http request... (" + + std::to_string(rc) + ", " + + std::to_string(errno) + ")\n" + http); + } + + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { + if (SocketT::recv(line + i, 1) == 0) { + throw FTL_Error("Connection closed by remote"); + } + } + + line[i] = 0; + if (i == 255) { + throw FTL_Error("Got invalid status line connecting to: " + uri.getHost()); + } + if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { + throw FTL_Error("ERROR: Got bad status connecting to: " + + uri.getHost() + ": " + line); + } + + // TODO(Seb): verify response headers, + while (true) { + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { + if (SocketT::recv(line+i, 1) == 0) { + throw FTL_Error("Connection closed by remote"); + } + } + if (line[0] == '\r' && line[1] == '\n') { break; } + } } template<typename SocketT> bool WebSocketBase<SocketT>::prepare_next(char* data, size_t data_len, size_t& offset) { - - offset = 0; - - // Header may be smaller than 14 bytes. If there isn't enough data, - // do not process before receiving more data. - if (data_len < 14) { return false; } - - wsheader_type header; - if (!ws_parse(data, data_len, header)) { - throw FTL_Error("corrupted WS header"); - } - - if ((header.N + header.header_size) > data_len) { - /*LOG(WARNING) << "buffered: " << data_len - << ", ws frame size: " << (header.N + header.header_size) - << " (not enough data in buffer)";*/ // noisy - return false; - } - - if (header.mask) { - throw FTL_Error("masked WebSocket data not supported"); // TODO - } - - // payload/application data/extension of control frames should be ignored? - // fragments are OK (data is be in order and frames are not interleaved) - - offset = header.header_size; - return true; + offset = 0; + + // Header may be smaller than 14 bytes. If there isn't enough data, + // do not process before receiving more data. + if (data_len < 14) { return false; } + + wsheader_type header; + if (!ws_parse(data, data_len, &header)) { + throw FTL_Error("corrupted WS header"); + } + + if ((header.N + header.header_size) > data_len) { + /*LOG(WARNING) << "buffered: " << data_len + << ", ws frame size: " << (header.N + header.header_size) + << " (not enough data in buffer)"; */ + return false; + } + + if (header.mask) { + throw FTL_Error("masked WebSocket data not supported"); // TODO(Seb): + } + + // payload/application data/extension of control frames should be ignored? + // fragments are OK (data is be in order and frames are not interleaved) + + offset = header.header_size; + return true; } template<typename SocketT> ssize_t WebSocketBase<SocketT>::writev(const struct iovec *iov, int iovcnt) { - if ((iovcnt + 1) >= ssize_t(iovecs_.size())) { iovecs_.resize(iovcnt + 1); } - // copy iovecs to local buffer, first iovec entry reserved for header - std::copy(iov, iov + iovcnt, iovecs_.data() + 1); - - // masking - size_t msglen = 0; - uint32_t mask = secure_rnd(); - uint8_t* masking_key = reinterpret_cast<uint8_t*>(&mask); - - // calculate total size of message and mask it. - for (int i = 1; i < iovcnt + 1; i++) { - const size_t mlen = iovecs_[i].iov_len; - char *buf = (char*)iovecs_[i].iov_base; - - // TODO: Make this more efficient. - for (size_t j = 0; j != mlen; ++j) { - buf[j] ^= masking_key[(msglen + j)&0x3]; - } - msglen += mlen; - } - - // create header - constexpr size_t h_size = 20; - char h_buffer[h_size]; - - auto rc = ws_prepare(wsheader_type::BINARY_FRAME, true, mask, msglen, h_buffer, h_size); - if (rc < 0) { return -1; } - - // send header + data - iovecs_[0].iov_base = h_buffer; - iovecs_[0].iov_len = rc; - - auto sent = SocketT::writev(iovecs_.data(), iovcnt + 1); - if (sent > 0) { - // do not report sent header size - return sent - rc; - } - return sent; + if ((iovcnt + 1) >= ssize_t(iovecs_.size())) { iovecs_.resize(iovcnt + 1); } + // copy iovecs to local buffer, first iovec entry reserved for header + std::copy(iov, iov + iovcnt, iovecs_.data() + 1); + + // masking + size_t msglen = 0; + uint32_t mask = secure_rnd(); + uint8_t* masking_key = reinterpret_cast<uint8_t*>(&mask); + + // calculate total size of message and mask it. + for (int i = 1; i < iovcnt + 1; i++) { + const size_t mlen = iovecs_[i].iov_len; + char *buf = reinterpret_cast<char*>(iovecs_[i].iov_base); + + // TODO(Seb): Make this more efficient. + for (size_t j = 0; j != mlen; ++j) { + buf[j] ^= masking_key[(msglen + j)&0x3]; + } + msglen += mlen; + } + + // create header + constexpr size_t kHSize = 20; + char h_buffer[kHSize]; + + auto rc = ws_prepare(wsheader_type::BINARY_FRAME, true, mask, msglen, h_buffer, kHSize); + if (rc < 0) { return -1; } + + // send header + data + iovecs_[0].iov_base = h_buffer; + iovecs_[0].iov_len = rc; + + auto sent = SocketT::writev(iovecs_.data(), iovcnt + 1); + if (sent > 0) { + // do not report sent header size + return sent - rc; + } + return sent; } template<typename SocketT> ftl::URI::scheme_t WebSocketBase<SocketT>::scheme() const {return ftl::URI::SCHEME_TCP; } // explicit instantiation -template class WebSocketBase<Connection_TCP>; // Connection_WS +template class WebSocketBase<Connection_TCP>; // Connection_WS #ifdef HAVE_GNUTLS -template class WebSocketBase<Connection_TLS>; // Connection_WSS -#endif \ No newline at end of file +template class WebSocketBase<Connection_TLS>; // Connection_WSS +#endif diff --git a/src/protocol/websocket.hpp b/src/protocol/websocket.hpp index 8e73be1667d4b2996c5fa952294ed06dd629dbab..7d01503c8d0db54f4a3cf8782f999cf7c0b904d3 100644 --- a/src/protocol/websocket.hpp +++ b/src/protocol/websocket.hpp @@ -1,40 +1,42 @@ -#ifndef _FTL_NET_WEBSOCKET_HPP_ -#define _FTL_NET_WEBSOCKET_HPP_ +/** + * @file websocket.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ -#include "connection.hpp" -#include "tcp.hpp" -#include "tls.hpp" +#pragma once #include <vector> #include <random> +#include "connection.hpp" +#include "tcp.hpp" +#include "tls.hpp" + namespace ftl { namespace net { namespace internal { template<typename SocketT> class WebSocketBase : public SocketT { -public: - WebSocketBase(); - ftl::URI::scheme_t scheme() const override; - void connect(const ftl::URI& uri, int timeout=0) override; - - bool prepare_next(char* buffer, size_t len, size_t &offset) override; + public: + WebSocketBase(); + ftl::URI::scheme_t scheme() const override; + void connect(const ftl::URI& uri, int timeout = 0) override; - ssize_t writev(const struct iovec *iov, int iovcnt) override; + bool prepare_next(char* buffer, size_t len, size_t &offset) override; -protected: + ssize_t writev(const struct iovec *iov, int iovcnt) override; - // output io vectors (incl. header) - std::vector<struct iovec> iovecs_; + protected: + // output io vectors (incl. header) + std::vector<struct iovec> iovecs_; }; using Connection_WS = WebSocketBase<Connection_TCP>; #ifdef HAVE_GNUTLS using Connection_WSS = WebSocketBase<Connection_TLS>; #endif -} -} -} - -#endif +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/rpc.cpp b/src/rpc.cpp index ef4a6d183a684bad91bdbc2387b1534b9956ba3d..f0c275ab17c8024f89e36466c62faf14d622255f 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -1,3 +1,9 @@ +/** + * @file rpc.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include "rpc.hpp" #include "streams/netstream.hpp" diff --git a/src/rpc.hpp b/src/rpc.hpp index 03519d067c8faf69b8641b27888cf939ae2214fc..78a65500a6d9d7ce5c067e4efb658edc1fe664be 100644 --- a/src/rpc.hpp +++ b/src/rpc.hpp @@ -1,3 +1,9 @@ +/** + * @file rpc.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once #include <memory> @@ -11,5 +17,5 @@ namespace rpc { void install(ftl::net::Universe *net); -} -} +} // namespace rpc +} // namespace ftl diff --git a/src/self.cpp b/src/self.cpp index 85e49401143ad7f86f7bfbd55d7f5f060f9458ea..4aff76ab083c220eb1ab4d4f07fad3fe7d93b548 100644 --- a/src/self.cpp +++ b/src/self.cpp @@ -1,3 +1,9 @@ +/** + * @file self.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include "universe.hpp" #include <ftl/protocol/self.hpp> #include "./streams/netstream.hpp" @@ -18,7 +24,7 @@ std::shared_ptr<ftl::protocol::Stream> Self::createStream(const std::string &uri if (!u.isValid()) throw FTL_Error("Invalid Stream URI"); switch (u.getScheme()) { - case ftl::URI::SCHEME_FTL : return std::make_shared<ftl::protocol::Net>(uri, universe_.get(), true); + case ftl::URI::SCHEME_FTL : return std::make_shared<ftl::protocol::Net>(uri, universe_.get(), true); case ftl::URI::SCHEME_FILE : case ftl::URI::SCHEME_NONE : default : throw FTL_Error("Invalid Stream URI"); @@ -31,13 +37,13 @@ std::shared_ptr<ftl::protocol::Stream> Self::getStream(const std::string &uri) { if (!u.isValid()) throw FTL_Error("Invalid Stream URI"); switch (u.getScheme()) { - case ftl::URI::SCHEME_FTL : return std::make_shared<ftl::protocol::Net>(uri, universe_.get(), false); + case ftl::URI::SCHEME_FTL : return std::make_shared<ftl::protocol::Net>(uri, universe_.get(), false); case ftl::URI::SCHEME_FILE : case ftl::URI::SCHEME_NONE : default : throw FTL_Error("Invalid Stream URI"); } } - + void Self::start() { universe_->start(); } @@ -99,7 +105,11 @@ ftl::Handle Self::onDisconnect(const std::function<bool(const std::shared_ptr<ft }); } -ftl::Handle Self::onError(const std::function<bool(const std::shared_ptr<ftl::protocol::Node>&, ftl::protocol::Error, const std::string &)> &cb) { +using ErrorCb = std::function<bool( + const std::shared_ptr<ftl::protocol::Node>&, + ftl::protocol::Error, const std::string &)>; + +ftl::Handle Self::onError(const ErrorCb &cb) { return universe_->onError([cb](const ftl::net::PeerPtr &p, ftl::protocol::Error e, const std::string &estr) { return cb(std::make_shared<ftl::protocol::Node>(p), e, estr); }); diff --git a/src/socket.hpp b/src/socket.hpp index 896d4e6a9598eb562a48b7c85323b53df7ccb482..7353ae843ac4cd4f0e3cc32ffacacfa7f8df7d3d 100644 --- a/src/socket.hpp +++ b/src/socket.hpp @@ -13,6 +13,6 @@ namespace internal { class SocketConnection; class SocketServer; -} -} -} +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/socket/socket.cpp b/src/socket/socket.cpp index 511724ed3e45d55a33826e33718b7e6606d193e2..ffdf4c3c34ab6612439f133cb9ace34165ad48ca 100644 --- a/src/socket/socket.cpp +++ b/src/socket/socket.cpp @@ -1,3 +1,9 @@ +/** + * @file socket.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + // OS specific implementations for TCP sockets #include "../socketImpl.hpp" @@ -13,38 +19,38 @@ bool Socket::is_open() { return status_ == STATUS::OPEN; } bool Socket::is_closed() { return status_ == STATUS::CLOSED; } bool Socket::set_recv_buffer_size(size_t sz) { - int a = static_cast<int>(sz); - return setsockopt(SOL_SOCKET, SO_RCVBUF, (char *) &a, sizeof(int)) != -1; + int a = static_cast<int>(sz); + return setsockopt(SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&a), sizeof(int)) != -1; } bool Socket::set_send_buffer_size(size_t sz) { - int a = static_cast<int>(sz); - return setsockopt(SOL_SOCKET, SO_SNDBUF, (char *) &a, sizeof(int)) != -1; + int a = static_cast<int>(sz); + return setsockopt(SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&a), sizeof(int)) != -1; } size_t Socket::get_recv_buffer_size() { - int a = 0; - socklen_t optlen = sizeof(int); - getsockopt(SOL_SOCKET, SO_RCVBUF, (char *) &a, &optlen); - return a; + int a = 0; + socklen_t optlen = sizeof(int); + getsockopt(SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&a), &optlen); + return a; } size_t Socket::get_send_buffer_size() { - int a = 0; - socklen_t optlen = sizeof(int); - getsockopt(SOL_SOCKET, SO_SNDBUF, (char *) &a, &optlen); - return a; + int a = 0; + socklen_t optlen = sizeof(int); + getsockopt(SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&a), &optlen); + return a; } bool Socket::set_nodelay(bool val) { - int flags = val ? 1 : 0; - return setsockopt(IPPROTO_TCP, TCP_NODELAY, - (char *) &flags, sizeof(flags)) == 0; + int flags = val ? 1 : 0; + return setsockopt(IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast<char*>(&flags), sizeof(flags)) == 0; } bool Socket::get_nodelay() { - int flags = 0; - socklen_t len = sizeof(int); - getsockopt(IPPROTO_TCP, TCP_NODELAY, (char *) &flags, &len); - return flags; + int flags = 0; + socklen_t len = sizeof(int); + getsockopt(IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&flags), &len); + return flags; } \ No newline at end of file diff --git a/src/socket/socket_linux.cpp b/src/socket/socket_linux.cpp index 9ec8a6338cc044238f3ba1c897a64258f008e0b5..3d1b71a6e459d6bd461064f2eee4ef28a2a71d21 100644 --- a/src/socket/socket_linux.cpp +++ b/src/socket/socket_linux.cpp @@ -1,3 +1,9 @@ +/** + * @file socket_linux.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + #include <unistd.h> #include <sys/types.h> #include <sys/uio.h> @@ -5,7 +11,6 @@ #include <sys/socket.h> #include <netdb.h> #include <fcntl.h> - #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> @@ -19,210 +24,207 @@ using ftl::net::internal::SocketAddress; /// resolve address for OS socket calls, return true on success bool ftl::net::internal::resolve_inet_address(const std::string &hostname, int port, SocketAddress &address) { - addrinfo hints = {}, *addrs; - - // TODO: use uri for hints. Fixed values used here - // should work as long only TCP is used. - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - auto rc = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &addrs); - if (rc != 0 || addrs == nullptr) return false; - - address.len = (socklen_t) addrs->ai_addrlen; - memcpy(&address.addr, addrs->ai_addr, address.len); - freeaddrinfo(addrs); - return true; + addrinfo hints = {}, *addrs; + + // TODO(Seb): use uri for hints. Fixed values used here + // should work as long only TCP is used. + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + auto rc = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &addrs); + if (rc != 0 || addrs == nullptr) return false; + + address.len = (socklen_t) addrs->ai_addrlen; + memcpy(&address.addr, addrs->ai_addr, address.len); + freeaddrinfo(addrs); + return true; } // Socket Socket::Socket(int domain, int type, int protocol) : - status_(STATUS::UNCONNECTED), fd_(-1), family_(domain), err_(0) { - - int retval = socket(domain, type, protocol); + status_(STATUS::UNCONNECTED), fd_(-1), family_(domain), err_(0) { + int retval = socket(domain, type, protocol); - if (retval > 0) { - fd_ = retval; - } else { - LOG(ERROR) << ("socket() failed"); - throw FTL_Error("socket: " + get_error_string()); - } + if (retval > 0) { + fd_ = retval; + } else { + LOG(ERROR) << ("socket() failed"); + throw FTL_Error("socket: " + get_error_string()); + } } bool Socket::is_valid() { return status_ != STATUS::INVALID; } ssize_t Socket::recv(char *buffer, size_t len, int flags) { - return ::recv(fd_, buffer, len, flags); + return ::recv(fd_, buffer, len, flags); } ssize_t Socket::send(const char* buffer, size_t len, int flags) { - return ::send(fd_, buffer, len, flags); + return ::send(fd_, buffer, len, flags); } ssize_t Socket::writev(const struct iovec *iov, int iovcnt) { - return ::writev(fd_, iov, iovcnt); + return ::writev(fd_, iov, iovcnt); } int Socket::bind(const SocketAddress &addr) { - auto retval = ::bind(fd_, &addr.addr, addr.len); - if (retval) { - status_ = Socket::OPEN; - } - return retval; + auto retval = ::bind(fd_, &addr.addr, addr.len); + if (retval) { + status_ = Socket::OPEN; + } + return retval; } int Socket::listen(int backlog) { - return ::listen(fd_, backlog); + return ::listen(fd_, backlog); } Socket Socket::accept(SocketAddress &addr) { - addr.len = sizeof(SocketAddress); - Socket socket; - int retval = ::accept(fd_, &(addr.addr), &(addr.len)); - if (retval > 0) { - socket.status_ = STATUS::OPEN; - socket.fd_ = retval; - socket.family_ = family_; - } else { - LOG(ERROR) << "accept returned error: " << strerror(errno); - socket.status_ = STATUS::INVALID; - } - return socket; + addr.len = sizeof(SocketAddress); + Socket socket; + int retval = ::accept(fd_, &(addr.addr), &(addr.len)); + if (retval > 0) { + socket.status_ = STATUS::OPEN; + socket.fd_ = retval; + socket.family_ = family_; + } else { + LOG(ERROR) << "accept returned error: " << strerror(errno); + socket.status_ = STATUS::INVALID; + } + return socket; } int Socket::connect(const SocketAddress& address) { - - int err = 0; - if (status_ == STATUS::UNCONNECTED) { - err = ::connect(fd_, &address.addr, address.len); - if (err == 0) { - status_ = STATUS::OPEN; - return 0; - } - else - { - if (errno == EINPROGRESS) { - status_ = STATUS::OPEN; // close() will be called by destructor - // add better status code? - return -1; - } else { - status_ = STATUS::CLOSED; - ::close(fd_); - } - } - } - return err; + int err = 0; + if (status_ == STATUS::UNCONNECTED) { + err = ::connect(fd_, &address.addr, address.len); + if (err == 0) { + status_ = STATUS::OPEN; + return 0; + } else { + if (errno == EINPROGRESS) { + status_ = STATUS::OPEN; // close() will be called by destructor + // add better status code? + return -1; + } else { + status_ = STATUS::CLOSED; + ::close(fd_); + } + } + } + return err; } int Socket::connect(const SocketAddress &address, int timeout) { - if (timeout <= 0) { - return connect(address); - } - - bool blocking = is_blocking(); - if (blocking) set_blocking(false); - - auto rc = connect(address); - if (rc < 0) { - if (errno == EINPROGRESS) { - fd_set myset; - fd_set errset; - FD_ZERO(&myset); - FD_SET(fd_, &myset); - FD_ZERO(&errset); - FD_SET(fd_, &errset); - - struct timeval tv; - tv.tv_sec = timeout; - tv.tv_usec = 0; - - rc = select(fd_+1u, NULL, &myset, &errset, &tv); - if (FD_ISSET(fd_, &errset)) rc = -1; - } - } - - if (blocking) - set_blocking(true); - - if (rc < 0) { - ::close(fd_); - status_ = STATUS::CLOSED; - LOG(ERROR) << "socket error: " << strerror(errno); - return rc; - } - - return 0; + if (timeout <= 0) { + return connect(address); + } + + bool blocking = is_blocking(); + if (blocking) set_blocking(false); + + auto rc = connect(address); + if (rc < 0) { + if (errno == EINPROGRESS) { + fd_set myset; + fd_set errset; + FD_ZERO(&myset); + FD_SET(fd_, &myset); + FD_ZERO(&errset); + FD_SET(fd_, &errset); + + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + + rc = select(fd_+1u, NULL, &myset, &errset, &tv); + if (FD_ISSET(fd_, &errset)) rc = -1; + } + } + + if (blocking) + set_blocking(true); + + if (rc < 0) { + ::close(fd_); + status_ = STATUS::CLOSED; + LOG(ERROR) << "socket error: " << strerror(errno); + return rc; + } + + return 0; } /// Close socket (if open). Multiple calls are safe. bool Socket::close() { - if (is_valid() && status_ != STATUS::CLOSED) { - status_ = STATUS::CLOSED; - return ::close(fd_) == 0; - } else if (status_ != STATUS::CLOSED) { - LOG(INFO) << "close() on non-valid socket"; - } - return false; + if (is_valid() && status_ != STATUS::CLOSED) { + status_ = STATUS::CLOSED; + return ::close(fd_) == 0; + } else if (status_ != STATUS::CLOSED) { + LOG(INFO) << "close() on non-valid socket"; + } + return false; } int Socket::setsockopt(int level, int optname, const void *optval, socklen_t optlen) { - return ::setsockopt(fd_, level, optname, optval, optlen); + return ::setsockopt(fd_, level, optname, optval, optlen); } int Socket::getsockopt(int level, int optname, void *optval, socklen_t *optlen) { - return ::getsockopt(fd_, level, optname, optval, optlen); + return ::getsockopt(fd_, level, optname, optval, optlen); } void Socket::set_blocking(bool val) { - auto arg = fcntl(fd_, F_GETFL, NULL); - arg = val ? (arg | O_NONBLOCK) : (arg & ~O_NONBLOCK); - fcntl(fd_, F_SETFL, arg); + auto arg = fcntl(fd_, F_GETFL, NULL); + arg = val ? (arg | O_NONBLOCK) : (arg & ~O_NONBLOCK); + fcntl(fd_, F_SETFL, arg); } bool Socket::is_blocking() { - return fcntl(fd_, F_GETFL, NULL) & O_NONBLOCK; + return fcntl(fd_, F_GETFL, NULL) & O_NONBLOCK; } bool Socket::is_fatal(int code) { - int e = (code != 0) ? code : errno; - return !(e == EINTR || e == EWOULDBLOCK || e == EINPROGRESS); + int e = (code != 0) ? code : errno; + return !(e == EINTR || e == EWOULDBLOCK || e == EINPROGRESS); } std::string Socket::get_error_string(int code) { - return strerror((code != 0) ? code : errno); + return strerror((code != 0) ? code : errno); } // TCP socket Socket ftl::net::internal::create_tcp_socket() { - return Socket(AF_INET, SOCK_STREAM, 0); + return Socket(AF_INET, SOCK_STREAM, 0); } -std::string ftl::net::internal::get_host(SocketAddress& addr) { - char hbuf[1024]; - int err = getnameinfo(&(addr.addr), addr.len, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD); - if (err == 0) { return std::string(hbuf); } - else if (err == EAI_NONAME) { return ftl::net::internal::get_ip(addr); } - else { LOG(WARNING) << "getnameinfo(): " << gai_strerror(err) << " (" << err << ")"; } - return "unknown"; +std::string ftl::net::internal::get_host(const SocketAddress& addr) { + char hbuf[1024]; + int err = getnameinfo(&(addr.addr), addr.len, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD); + if (err == 0) { return std::string(hbuf); } + else if (err == EAI_NONAME) return ftl::net::internal::get_ip(addr); + else + LOG(WARNING) << "getnameinfo(): " << gai_strerror(err) << " (" << err << ")"; + return "unknown"; } SocketAddress Socket::getsockname() { - SocketAddress addr; - auto* a = reinterpret_cast<struct sockaddr*>(&(addr.addr)); - ::getsockname(fd_, a, &(addr.len)); - return addr; + SocketAddress addr; + auto* a = reinterpret_cast<struct sockaddr*>(&(addr.addr)); + ::getsockname(fd_, a, &(addr.len)); + return addr; } -std::string ftl::net::internal::get_ip(SocketAddress& addr) { - auto* addr_in = reinterpret_cast<struct sockaddr_in*>(&(addr.addr)); - std::string address(inet_ntoa(addr_in->sin_addr)); - return address; +std::string ftl::net::internal::get_ip(const SocketAddress& addr) { + auto* addr_in = reinterpret_cast<const sockaddr_in*>(&(addr.addr)); + std::string address(inet_ntoa(addr_in->sin_addr)); + return address; } -int ftl::net::internal::get_port(SocketAddress& addr) { - auto* addr_in = reinterpret_cast<struct sockaddr_in*>(&(addr.addr)); - return htons(addr_in->sin_port); +int ftl::net::internal::get_port(const SocketAddress& addr) { + auto* addr_in = reinterpret_cast<const sockaddr_in*>(&(addr.addr)); + return htons(addr_in->sin_port); } diff --git a/src/socket/socket_windows.cpp b/src/socket/socket_windows.cpp index c2beff08a58439392ebc47bb27d795ee7fb78530..c4c855995c3872a85a80826adf4c314b1c7af2eb 100644 --- a/src/socket/socket_windows.cpp +++ b/src/socket/socket_windows.cpp @@ -1,16 +1,22 @@ +/** + * @file socket_windows.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <atomic> +#include <string> + #include "../src/socket.hpp" #include <ftl/exception.hpp> #include <ftl/lib/loguru.hpp> -#include <atomic> -#include <winsock2.h> -#include <ws2tcpip.h> #pragma comment(lib, "Ws2_32.lib") -#include <string> - // winsock2 documentation // https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ @@ -18,228 +24,223 @@ using ftl::net::internal::Socket; using ftl::net::internal::SocketAddress; bool ftl::net::internal::resolve_inet_address(const std::string& hostname, int port, SocketAddress& address) { - addrinfo hints = {}, *addrs; + addrinfo hints = {}, *addrs; - // TODO: use uri for hints. Fixed values used here - // should work as long only TCP is used. - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; + // TODO(Seb): use uri for hints. Fixed values used here + // should work as long only TCP is used. + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; - auto rc = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &addrs); - if (rc != 0 || addrs == nullptr) return false; - address = *((sockaddr_in*) (addrs->ai_addr)); - freeaddrinfo(addrs); - return true; + auto rc = getaddrinfo(hostname.c_str(), std::to_string(port).c_str(), &hints, &addrs); + if (rc != 0 || addrs == nullptr) return false; + address = *(reinterpret_cast<sockaddr_in*>(addrs->ai_addr)); + freeaddrinfo(addrs); + return true; } static std::atomic_bool is_initialized_; static WSAData wsaData_; Socket::Socket(int domain, int type, int protocol) : - status_(STATUS::UNCONNECTED), fd_(-1), family_(domain) { - - if (!is_initialized_.exchange(true)) { - if (WSAStartup(MAKEWORD(1, 1), &wsaData_) != 0) { - LOG(FATAL) << "could not initialize sockets"; - // is it possible to retry/recover? - } - } - - // TODO: initialization might not be complete if called from another thread - fd_ = ::socket(domain, type, protocol); - if (fd_ == INVALID_SOCKET) { - err_ = WSAGetLastError(); - throw FTL_Error("socket() failed" + get_error_string()); - } + status_(STATUS::UNCONNECTED), fd_(-1), family_(domain) { + if (!is_initialized_.exchange(true)) { + if (WSAStartup(MAKEWORD(1, 1), &wsaData_) != 0) { + LOG(FATAL) << "could not initialize sockets"; + // is it possible to retry/recover? + } + } + + // TODO(Seb): initialization might not be complete if called from another thread + fd_ = ::socket(domain, type, protocol); + if (fd_ == INVALID_SOCKET) { + err_ = WSAGetLastError(); + throw FTL_Error("socket() failed" + get_error_string()); + } } bool Socket::is_valid() { - return fd_ != INVALID_SOCKET; + return fd_ != INVALID_SOCKET; } ssize_t Socket::recv(char* buffer, size_t len, int flags) { - auto err = ::recv(fd_, buffer, len, flags); - if (err < 0) { err_ = WSAGetLastError(); } - return err; + auto err = ::recv(fd_, buffer, len, flags); + if (err < 0) { err_ = WSAGetLastError(); } + return err; } ssize_t Socket::send(const char* buffer, size_t len, int flags) { - return ::send(fd_, buffer, len, flags); + return ::send(fd_, buffer, len, flags); } ssize_t Socket::writev(const struct iovec* iov, int iovcnt) { + std::vector<WSABUF> wsabuf(iovcnt); - std::vector<WSABUF> wsabuf(iovcnt); - - for (int i = 0; i < iovcnt; i++) { - wsabuf[i].len = (ULONG)(iov[i].iov_len); - wsabuf[i].buf = (char*)(iov[i].iov_base); - } - - DWORD bytessent; - auto err = WSASend(fd_, wsabuf.data(), static_cast<DWORD>(wsabuf.size()), (LPDWORD)&bytessent, 0, NULL, NULL); - if (err < 0) { err_ = WSAGetLastError(); } - return (err < 0) ? err : bytessent; + for (int i = 0; i < iovcnt; i++) { + wsabuf[i].len = (ULONG)(iov[i].iov_len); + wsabuf[i].buf = reinterpret_cast<char*>(iov[i].iov_base); + } + DWORD bytessent; + auto err = WSASend(fd_, wsabuf.data(), static_cast<DWORD>(wsabuf.size()), (LPDWORD)&bytessent, 0, NULL, NULL); + if (err < 0) { err_ = WSAGetLastError(); } + return (err < 0) ? err : bytessent; } int Socket::bind(const SocketAddress& addr) { - int retval = ::bind(fd_, (sockaddr*)(&addr), sizeof(addr)); - if (retval == 0) { - status_ = STATUS::OPEN; - return 0; - } - else { - err_ = WSAGetLastError(); - ::closesocket(fd_); - status_ = STATUS::CLOSED; - fd_ = INVALID_SOCKET; - } - return -1; + int retval = ::bind(fd_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)); + if (retval == 0) { + status_ = STATUS::OPEN; + return 0; + } else { + err_ = WSAGetLastError(); + ::closesocket(fd_); + status_ = STATUS::CLOSED; + fd_ = INVALID_SOCKET; + } + return -1; } int Socket::listen(int backlog) { - int retval = ::listen(fd_, backlog); - if (retval == 0) { - return 0; - } - else { - err_ = WSAGetLastError(); - ::closesocket(fd_); - status_ = STATUS::CLOSED; - fd_ = INVALID_SOCKET; - return retval; - } + int retval = ::listen(fd_, backlog); + if (retval == 0) { + return 0; + } else { + err_ = WSAGetLastError(); + ::closesocket(fd_); + status_ = STATUS::CLOSED; + fd_ = INVALID_SOCKET; + return retval; + } } Socket Socket::accept(SocketAddress& addr) { - Socket socket; - int addrlen = sizeof(addr); - int retval = ::accept(fd_, (sockaddr*)(&addr), &addrlen); - - if (retval > 0) { - socket.status_ = STATUS::OPEN; - socket.fd_ = retval; - socket.family_ = family_; - } else { - err_ = WSAGetLastError(); - LOG(ERROR) << "accept returned error: " << get_error_string(); - socket.status_ = STATUS::INVALID; - } - return socket; + Socket socket; + int addrlen = sizeof(addr); + int retval = ::accept(fd_, reinterpret_cast<sockaddr*>(&addr), &addrlen); + + if (retval > 0) { + socket.status_ = STATUS::OPEN; + socket.fd_ = retval; + socket.family_ = family_; + } else { + err_ = WSAGetLastError(); + LOG(ERROR) << "accept returned error: " << get_error_string(); + socket.status_ = STATUS::INVALID; + } + return socket; } int Socket::connect(const SocketAddress& address) { - int err = 0; - if (status_ != STATUS::UNCONNECTED) { - return -1; - } - - err = ::connect(fd_, (sockaddr*)(&address), sizeof(SOCKADDR)); - if (err == 0) { - status_ = STATUS::OPEN; - return 0; - - } else { - err_ = WSAGetLastError(); - if (err_ == EINPROGRESS) { - status_ = STATUS::OPEN; - return -1; - } else { - ::closesocket(fd_); - status_ = STATUS::CLOSED; - fd_ = INVALID_SOCKET; - } - } - return -1; + int err = 0; + if (status_ != STATUS::UNCONNECTED) { + return -1; + } + + err = ::connect(fd_, reinterpret_cast<const sockaddr*>(&address), sizeof(SOCKADDR)); + if (err == 0) { + status_ = STATUS::OPEN; + return 0; + + } else { + err_ = WSAGetLastError(); + if (err_ == EINPROGRESS) { + status_ = STATUS::OPEN; + return -1; + } else { + ::closesocket(fd_); + status_ = STATUS::CLOSED; + fd_ = INVALID_SOCKET; + } + } + return -1; } int Socket::connect(const SocketAddress& address, int timeout) { - // connect() blocks on Windows - return connect(address); + // connect() blocks on Windows + return connect(address); } bool Socket::close() { - bool retval = true; - if (is_valid() && status_ != STATUS::CLOSED) { - status_ = STATUS::CLOSED; - retval = closesocket(fd_) == 0; - err_ = errno; - } - fd_ = INVALID_SOCKET; - return retval; + bool retval = true; + if (is_valid() && status_ != STATUS::CLOSED) { + status_ = STATUS::CLOSED; + retval = closesocket(fd_) == 0; + err_ = errno; + } + fd_ = INVALID_SOCKET; + return retval; } int Socket::setsockopt(int level, int optname, const void* optval, socklen_t optlen) { - return ::setsockopt(fd_, level, optname, (const char*)optval, optlen); + return ::setsockopt(fd_, level, optname, (const char*)optval, optlen); } int Socket::getsockopt(int level, int optname, void* optval, socklen_t* optlen) { - return ::getsockopt(fd_, level, optname, (char*)optval, optlen); + return ::getsockopt(fd_, level, optname, reinterpret_cast<char*>(optval), optlen); } void Socket::set_blocking(bool val) { - LOG(ERROR) << "TODO: set blocking/non-blocking"; + LOG(ERROR) << "TODO: set blocking/non-blocking"; } std::string Socket::get_error_string(int code) { - wchar_t* s = NULL; - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, (code != 0) ? code : err_, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&s, 0, NULL); - if (!s) { - return "Unknown"; - } - std::wstring ws(s); - std::string msg(ws.begin(), ws.end()); - LocalFree(s); - return msg; + wchar_t* s = NULL; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, (code != 0) ? code : err_, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&s, 0, NULL); + if (!s) { + return "Unknown"; + } + std::wstring ws(s); + std::string msg(ws.begin(), ws.end()); + LocalFree(s); + return msg; } bool Socket::is_fatal(int code) { - if (code != 0) err_ = code; - return !(err_ == 0 || err_ == WSAEINTR || err_ == WSAEMSGSIZE || err_ == WSAEINPROGRESS || err_ == WSAEWOULDBLOCK); + if (code != 0) err_ = code; + return !(err_ == 0 || err_ == WSAEINTR || err_ == WSAEMSGSIZE || err_ == WSAEINPROGRESS || err_ == WSAEWOULDBLOCK); } bool Socket::is_blocking() { - return false; + return false; } // TCP socket Socket ftl::net::internal::create_tcp_socket() { - return Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + return Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } -std::string ftl::net::internal::get_host(SocketAddress& addr) { - constexpr int len_max = 512; - char hostname[len_max]; - char servinfo[len_max]; - auto retval = getnameinfo((struct sockaddr*)&addr, - sizeof(struct sockaddr), hostname, - len_max, servinfo, len_max, NI_NUMERICSERV); +std::string ftl::net::internal::get_host(const SocketAddress& addr) { + constexpr int kLenMax = 512; + char hostname[kLenMax]; + char servinfo[kLenMax]; + auto retval = getnameinfo((struct sockaddr*)&addr, + sizeof(struct sockaddr), hostname, + kLenMax, servinfo, kLenMax, NI_NUMERICSERV); - if (retval != 0) { - return "N/A"; - } else { - return std::string(hostname); - } + if (retval != 0) { + return "N/A"; + } else { + return std::string(hostname); + } } SocketAddress Socket::getsockname() { - SocketAddress addr; - socklen_t len = sizeof(SocketAddress); - ::getsockname(fd_, (struct sockaddr *)&addr, &len); - return addr; + SocketAddress addr; + socklen_t len = sizeof(SocketAddress); + ::getsockname(fd_, (struct sockaddr *)&addr, &len); + return addr; } -std::string ftl::net::internal::get_ip(SocketAddress& addr) { - char buf[64]; - inet_ntop(addr.sin_family, &(addr.sin_addr), buf, sizeof(buf)); - return std::string(buf); +std::string ftl::net::internal::get_ip(const SocketAddress& addr) { + char buf[64]; + inet_ntop(addr.sin_family, &(addr.sin_addr), buf, sizeof(buf)); + return std::string(buf); } -int ftl::net::internal::get_port(SocketAddress& addr) { - return htons(addr.sin_port); +int ftl::net::internal::get_port(const SocketAddress& addr) { + return htons(addr.sin_port); } diff --git a/src/socket/types.hpp b/src/socket/types.hpp index 42fdd7dcf473eb31b8850a9796ea778d11286795..66f5a356f0f664baf89867dfc578a2c7a27179ac 100644 --- a/src/socket/types.hpp +++ b/src/socket/types.hpp @@ -1,5 +1,10 @@ -#ifndef _FTL_NET_SOCKETS_TYPES_HPP_ -#define _FTL_NET_SOCKETS_TYPES_HPP_ +/** + * @file types.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Sebastian Hahta + */ + +#pragma once #if defined(WIN32) // Windows @@ -14,8 +19,8 @@ typedef SSIZE_T ssize_t; // defined by msgpack, do not redefine here /* typedef struct iovec { - void* iov_base; - size_t iov_len; + void* iov_base; + size_t iov_len; }; */ @@ -39,13 +44,11 @@ typedef sockaddr_in SocketAddress; typedef int socket_t; struct SocketAddress { - socklen_t len = sizeof(struct sockaddr); - struct sockaddr addr; + socklen_t len = sizeof(struct sockaddr); + struct sockaddr addr; }; #endif -} // internal -} // net -} // ftl - -#endif // _FTL_NET_SOCKETS_TYPES_HPP_ \ No newline at end of file +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/socketImpl.hpp b/src/socketImpl.hpp index 91371d9f71ec81b178ee58c3f2871a19b0d3bdda..6fbdddeb6ef09bb49e672801cf06099ccc6ec69b 100644 --- a/src/socketImpl.hpp +++ b/src/socketImpl.hpp @@ -1,3 +1,9 @@ +/** + * @file socket.hpp + * @copyright Copyright (c) 2020 University of Turku, MIT License + * @author Sebastian Hahta + */ + #pragma once #include <string> @@ -14,72 +20,71 @@ namespace internal { */ class Socket { -private: - enum STATUS { INVALID, UNCONNECTED, OPEN, CLOSED }; - STATUS status_; - socket_t fd_; - SocketAddress addr_; - int family_; - int err_; + private: + enum STATUS { INVALID, UNCONNECTED, OPEN, CLOSED }; + STATUS status_; + socket_t fd_; + SocketAddress addr_; + int family_; + int err_; -public: + public: + Socket(int domain, int type, int protocol); - Socket(int domain, int type, int protocol); + bool is_valid(); + bool is_open(); + bool is_closed(); + bool is_fatal(int code = 0); - bool is_valid(); - bool is_open(); - bool is_closed(); - bool is_fatal(int code=0); + ssize_t recv(char *buffer, size_t len, int flags); + ssize_t send(const char* buffer, size_t len, int flags); + ssize_t writev(const struct iovec *iov, int iovcnt); - ssize_t recv(char *buffer, size_t len, int flags); - ssize_t send(const char* buffer, size_t len, int flags); - ssize_t writev(const struct iovec *iov, int iovcnt); + int bind(const SocketAddress&); - int bind(const SocketAddress&); + int listen(int backlog); - int listen(int backlog); + Socket accept(SocketAddress&); - Socket accept(SocketAddress&); + int connect(const SocketAddress&); - int connect(const SocketAddress&); + /** Connect with timeout. Timeout implemented by changing socket temporarily + * to non-blocking mode and using select(). Uses connect(). + */ + int connect(const SocketAddress& addr, int timeout); - /** Connect with timeout. Timeout implemented by changing socket temporarily - * to non-blocking mode and using select(). Uses connect(). - */ - int connect(const SocketAddress& addr, int timeout); + /// Close socket (if open). Multiple calls are safe. + bool close(); - /// Close socket (if open). Multiple calls are safe. - bool close(); + Socket() : status_(STATUS::INVALID), fd_(-1), family_(-1), err_(0) {} - Socket() : status_(STATUS::INVALID), fd_(-1), family_(-1), err_(0) {} + /// Get the socket file descriptor. + socket_t fd() const { return fd_; } - /// Get the socket file descriptor. - socket_t fd() const { return fd_; } + bool set_recv_buffer_size(size_t sz); - bool set_recv_buffer_size(size_t sz); + bool set_send_buffer_size(size_t sz); - bool set_send_buffer_size(size_t sz); + size_t get_recv_buffer_size(); - size_t get_recv_buffer_size(); + size_t get_send_buffer_size(); - size_t get_send_buffer_size(); + void set_blocking(bool val); - void set_blocking(bool val); - - bool is_blocking(); + bool is_blocking(); - std::string get_error_string(int code=0); + std::string get_error_string(int code = 0); - // only valid for TCP sockets - bool set_nodelay(bool val); - bool get_nodelay(); + // only valid for TCP sockets + bool set_nodelay(bool val); + bool get_nodelay(); - SocketAddress getsockname(); + SocketAddress getsockname(); - // TODO: perhaps remove and implement in custom methods instead - int setsockopt(int level, int optname, const void *optval, socklen_t optlen); - int getsockopt(int level, int optname, void *optval, socklen_t *optlen); + // TODO(Seb): perhaps remove and implement in custom methods instead + int setsockopt(int level, int optname, const void *optval, socklen_t optlen); + int getsockopt(int level, int optname, void *optval, socklen_t *optlen); }; Socket create_tcp_socket(); @@ -88,11 +93,11 @@ Socket create_tcp_socket(); bool resolve_inet_address(const std::string &hostname, int port, SocketAddress& address); // add new functions for other socket types -// TODO assumes ipv4, add protocol info to SocketAddress structure? -std::string get_ip(SocketAddress& address); -std::string get_host(SocketAddress& address); -int get_port(SocketAddress& address); +// TODO(Seb): assumes ipv4, add protocol info to SocketAddress structure? +std::string get_ip(const SocketAddress& address); +std::string get_host(const SocketAddress& address); +int get_port(const SocketAddress& address); -} // namespace internal -} // namespace net -} // namespace ftl +} // namespace internal +} // namespace net +} // namespace ftl diff --git a/src/streams/broadcaster.cpp b/src/streams/broadcaster.cpp index 96a017406a9ea199dd2fcbac6300c02d973a7fda..5afb6c3e895accc2cb3c6118fbdce61e0b29d183 100644 --- a/src/streams/broadcaster.cpp +++ b/src/streams/broadcaster.cpp @@ -1,3 +1,9 @@ +/** + * @file broadcaster.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol/broadcaster.hpp> using ftl::protocol::Broadcast; @@ -6,148 +12,138 @@ using ftl::protocol::Packet; using ftl::protocol::Channel; using ftl::protocol::FrameID; -Broadcast::Broadcast() { - -} +Broadcast::Broadcast() {} -Broadcast::~Broadcast() { - -} +Broadcast::~Broadcast() {} void Broadcast::add(const std::shared_ptr<Stream> &s) { - UNIQUE_LOCK(mtx_,lk); + UNIQUE_LOCK(mtx_, lk); - auto &entry = streams_.emplace_back(); - entry.stream = s; + auto &entry = streams_.emplace_back(); + entry.stream = s; - entry.handle = std::move(s->onPacket([this,s](const StreamPacket &spkt, const Packet &pkt) { - trigger(spkt, pkt); - return true; - })); + entry.handle = std::move(s->onPacket([this, s](const StreamPacket &spkt, const Packet &pkt) { + trigger(spkt, pkt); + return true; + })); - entry.avail_handle = std::move(s->onAvailable([this,s](FrameID id, Channel channel) { - seen(id, channel); - return true; - })); + entry.avail_handle = std::move(s->onAvailable([this, s](FrameID id, Channel channel) { + seen(id, channel); + return true; + })); - entry.req_handle = std::move(s->onRequest([this,s](const ftl::protocol::Request &req) { - request(req); - return true; - })); + entry.req_handle = std::move(s->onRequest([this, s](const ftl::protocol::Request &req) { + request(req); + return true; + })); } void Broadcast::remove(const std::shared_ptr<Stream> &s) { - UNIQUE_LOCK(mtx_,lk); - for (auto it = streams_.begin(); it != streams_.end(); ++it) { - if (it->stream == s) { - it->handle.cancel(); - it->req_handle.cancel(); - it->avail_handle.cancel(); - streams_.erase(it); - break; - } - } + UNIQUE_LOCK(mtx_, lk); + for (auto it = streams_.begin(); it != streams_.end(); ++it) { + if (it->stream == s) { + it->handle.cancel(); + it->req_handle.cancel(); + it->avail_handle.cancel(); + streams_.erase(it); + break; + } + } } void Broadcast::clear() { - UNIQUE_LOCK(mtx_,lk); - streams_.clear(); + UNIQUE_LOCK(mtx_, lk); + streams_.clear(); } bool Broadcast::post(const StreamPacket &spkt, const Packet &pkt) { - //SHARED_LOCK(mtx_, lk); - - bool status = true; - for (auto &s : streams_) { - status = s.stream->post(spkt, pkt) && status; - } - return status; + bool status = true; + for (auto &s : streams_) { + status = s.stream->post(spkt, pkt) && status; + } + return status; } bool Broadcast::begin() { - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->begin(); - } - return r; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->begin(); + } + return r; } bool Broadcast::end() { - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->end(); - } - return r; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->end(); + } + return r; } bool Broadcast::active() { - if (streams_.size() == 0) return false; - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->active(); - } - return r; + if (streams_.size() == 0) return false; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->active(); + } + return r; } void Broadcast::reset() { - SHARED_LOCK(mtx_, lk); - for (auto &s : streams_) { - s.stream->reset(); - } + SHARED_LOCK(mtx_, lk); + for (auto &s : streams_) { + s.stream->reset(); + } } -void Broadcast::refresh() { - -} +void Broadcast::refresh() {} bool Broadcast::enable(FrameID id) { - bool r = false; - { - SHARED_LOCK(mtx_, lk); - for (auto &s : streams_) { - r = s.stream->enable(id) || r; - }; - } - if (r) Stream::enable(id); - return r; + bool r = false; + { + SHARED_LOCK(mtx_, lk); + for (auto &s : streams_) { + r = s.stream->enable(id) || r; + } + } + if (r) Stream::enable(id); + return r; } bool Broadcast::enable(FrameID id, ftl::protocol::Channel channel) { - bool r = false; - { - SHARED_LOCK(mtx_, lk); - for (auto &s : streams_) { - r = s.stream->enable(id, channel) || r; - }; - } - if (r) Stream::enable(id, channel); - return r; + bool r = false; + { + SHARED_LOCK(mtx_, lk); + for (auto &s : streams_) { + r = s.stream->enable(id, channel) || r; + } + } + if (r) Stream::enable(id, channel); + return r; } bool Broadcast::enable(FrameID id, const ftl::protocol::ChannelSet &channels) { - bool r = false; - { - SHARED_LOCK(mtx_, lk); - for (auto &s : streams_) { - r = s.stream->enable(id, channels) || r; - }; - } - if (r) Stream::enable(id, channels); - return r; + bool r = false; + { + SHARED_LOCK(mtx_, lk); + for (auto &s : streams_) { + r = s.stream->enable(id, channels) || r; + } + } + if (r) Stream::enable(id, channels); + return r; } -void Broadcast::setProperty(ftl::protocol::StreamProperty opt, std::any value) { - -} +void Broadcast::setProperty(ftl::protocol::StreamProperty opt, std::any value) {} std::any Broadcast::getProperty(ftl::protocol::StreamProperty opt) { - return 0; + return 0; } bool Broadcast::supportsProperty(ftl::protocol::StreamProperty opt) { - return false; + return false; } ftl::protocol::StreamType Broadcast::type() const { - return ftl::protocol::StreamType::kUnknown; + return ftl::protocol::StreamType::kUnknown; } diff --git a/src/streams/muxer.cpp b/src/streams/muxer.cpp index f790b12ce33564822428a365ca0c5abc9505edea..a3ec9ad753de61e6f45d439106bac93a73cef438 100644 --- a/src/streams/muxer.cpp +++ b/src/streams/muxer.cpp @@ -1,3 +1,9 @@ +/** + * @file muxer.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol/muxer.hpp> #include <ftl/lib/loguru.hpp> @@ -7,223 +13,220 @@ using ftl::protocol::StreamPacket; using ftl::protocol::FrameID; using ftl::protocol::StreamType; -Muxer::Muxer() { - -} +Muxer::Muxer() {} Muxer::~Muxer() { - UNIQUE_LOCK(mutex_,lk); - for (auto &se : streams_) { - se.handle.cancel(); - se.req_handle.cancel(); - se.avail_handle.cancel(); - } + UNIQUE_LOCK(mutex_, lk); + for (auto &se : streams_) { + se.handle.cancel(); + se.req_handle.cancel(); + se.avail_handle.cancel(); + } } FrameID Muxer::_mapFromInput(Muxer::StreamEntry *s, FrameID id) { - SHARED_LOCK(mutex_,lk); - int64_t iid = (int64_t(s->id) << 32) | id.id; - auto it = imap_.find(iid); - if (it != imap_.end()) { - return it->second; - } else { - // Otherwise allocate something. - lk.unlock(); - UNIQUE_LOCK(mutex_,ulk); - - FrameID newID; - if (s->fixed_fs >= 0) { - int source = sourcecount_[s->fixed_fs]++; - newID = FrameID(s->fixed_fs, source); - } else { - int fsiid = (s->id << 16) | id.frameset(); - if (fsmap_.count(fsiid) == 0) fsmap_[fsiid] = framesets_++; - newID = FrameID(fsmap_[fsiid], id.source()); - } - - imap_[iid] = newID; - auto &op = omap_[newID]; - op.first = id; - op.second = s; - //LOG(INFO) << "ADD MAPPING " << newID.frameset() << " - " << newID.source(); - return newID; - } + SHARED_LOCK(mutex_, lk); + int64_t iid = (int64_t(s->id) << 32) | id.id; + auto it = imap_.find(iid); + if (it != imap_.end()) { + return it->second; + } else { + // Otherwise allocate something. + lk.unlock(); + UNIQUE_LOCK(mutex_, ulk); + + FrameID newID; + if (s->fixed_fs >= 0) { + int source = sourcecount_[s->fixed_fs]++; + newID = FrameID(s->fixed_fs, source); + } else { + int fsiid = (s->id << 16) | id.frameset(); + if (fsmap_.count(fsiid) == 0) fsmap_[fsiid] = framesets_++; + newID = FrameID(fsmap_[fsiid], id.source()); + } + + imap_[iid] = newID; + auto &op = omap_[newID]; + op.first = id; + op.second = s; + return newID; + } } std::pair<FrameID, Muxer::StreamEntry*> Muxer::_mapToOutput(FrameID id) const { - SHARED_LOCK(mutex_,lk); - auto it = omap_.find(id); - if (it != omap_.end()) { - return it->second; - } else { - // Bad - //LOG(ERROR) << "NO OUTPUT MAPPING " << id.frameset() << " - " << id.source(); - return {id, nullptr}; - } + SHARED_LOCK(mutex_, lk); + auto it = omap_.find(id); + if (it != omap_.end()) { + return it->second; + } else { + return {id, nullptr}; + } } void Muxer::add(const std::shared_ptr<Stream> &s, int fsid) { - UNIQUE_LOCK(mutex_,lk); - - auto &se = streams_.emplace_back(); - se.id = stream_ids_++; - se.stream = s; - se.fixed_fs = fsid; - Muxer::StreamEntry *ptr = &se; - - se.handle = std::move(s->onPacket([this,ptr](const StreamPacket &spkt, const Packet &pkt) { - FrameID newID = _mapFromInput(ptr, FrameID(spkt.streamID, spkt.frame_number)); - - StreamPacket spkt2 = spkt; - spkt2.streamID = newID.frameset(); - spkt2.frame_number = newID.source(); - - trigger(spkt2, pkt); - return true; - })); - - se.avail_handle = std::move(s->onAvailable([this,ptr](FrameID id, Channel channel) { - FrameID newID = _mapFromInput(ptr, id); - seen(newID, channel); - return true; - })); - - se.req_handle = std::move(s->onRequest([this,ptr](const Request &req) { - FrameID newID = _mapFromInput(ptr, req.id); - Request newRequest = req; - newRequest.id = newID; - request(newRequest); - return true; - })); - - se.err_handle = std::move(s->onError([this](ftl::protocol::Error err, const std::string &str) { - error(err, str); - return true; - })); + UNIQUE_LOCK(mutex_, lk); + + auto &se = streams_.emplace_back(); + se.id = stream_ids_++; + se.stream = s; + se.fixed_fs = fsid; + Muxer::StreamEntry *ptr = &se; + + se.handle = std::move(s->onPacket([this, ptr](const StreamPacket &spkt, const Packet &pkt) { + FrameID newID = _mapFromInput(ptr, FrameID(spkt.streamID, spkt.frame_number)); + + StreamPacket spkt2 = spkt; + spkt2.streamID = newID.frameset(); + spkt2.frame_number = newID.source(); + + trigger(spkt2, pkt); + return true; + })); + + se.avail_handle = std::move(s->onAvailable([this, ptr](FrameID id, Channel channel) { + FrameID newID = _mapFromInput(ptr, id); + seen(newID, channel); + return true; + })); + + se.req_handle = std::move(s->onRequest([this, ptr](const Request &req) { + FrameID newID = _mapFromInput(ptr, req.id); + Request newRequest = req; + newRequest.id = newID; + request(newRequest); + return true; + })); + + se.err_handle = std::move(s->onError([this](ftl::protocol::Error err, const std::string &str) { + error(err, str); + return true; + })); } void Muxer::remove(const std::shared_ptr<Stream> &s) { - UNIQUE_LOCK(mutex_,lk); - for (auto i = streams_.begin(); i != streams_.end(); ++i) { - if (i->stream == s) { - auto *se = &(*i); - - se->handle.cancel(); - se->req_handle.cancel(); - se->avail_handle.cancel(); - - // Cleanup imap and omap - for (auto j = imap_.begin(); j != imap_.end();) { - const auto &e = *j; - if (e.first >> 32 == se->id) j = imap_.erase(j); - else ++j; - } - for (auto j = omap_.begin(); j != omap_.end();) { - const auto &e = *j; - if (e.second.second == se) j = omap_.erase(j); - else ++j; - } - - streams_.erase(i); - return; - } - } + UNIQUE_LOCK(mutex_, lk); + for (auto i = streams_.begin(); i != streams_.end(); ++i) { + if (i->stream == s) { + auto *se = &(*i); + + se->handle.cancel(); + se->req_handle.cancel(); + se->avail_handle.cancel(); + + // Cleanup imap and omap + for (auto j = imap_.begin(); j != imap_.end();) { + const auto &e = *j; + if (e.first >> 32 == se->id) j = imap_.erase(j); + else + ++j; + } + for (auto j = omap_.begin(); j != omap_.end();) { + const auto &e = *j; + if (e.second.second == se) j = omap_.erase(j); + else + ++j; + } + + streams_.erase(i); + return; + } + } } std::shared_ptr<Stream> Muxer::originStream(FrameID id) const { - auto p = _mapToOutput(id); - return (p.second) ? p.second->stream : nullptr; + auto p = _mapToOutput(id); + return (p.second) ? p.second->stream : nullptr; } bool Muxer::post(const StreamPacket &spkt, const Packet &pkt) { - auto p = _mapToOutput(FrameID(spkt.streamID, spkt.frame_number)); - if (!p.second) return false; - StreamPacket spkt2 = spkt; - spkt2.streamID = p.first.frameset(); - spkt2.frame_number = p.first.source(); - return p.second->stream->post(spkt2, pkt); + auto p = _mapToOutput(FrameID(spkt.streamID, spkt.frame_number)); + if (!p.second) return false; + StreamPacket spkt2 = spkt; + spkt2.streamID = p.first.frameset(); + spkt2.frame_number = p.first.source(); + return p.second->stream->post(spkt2, pkt); } bool Muxer::begin() { - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->begin(); - } - return r; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->begin(); + } + return r; } bool Muxer::end() { - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->end(); - } - return r; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->end(); + } + return r; } bool Muxer::active() { - bool r = true; - for (auto &s : streams_) { - r = r && s.stream->active(); - } - return r; + bool r = true; + for (auto &s : streams_) { + r = r && s.stream->active(); + } + return r; } void Muxer::reset() { - for (auto &s : streams_) { - s.stream->reset(); - } + for (auto &s : streams_) { + s.stream->reset(); + } } bool Muxer::enable(FrameID id) { - auto p = _mapToOutput(id); - if (!p.second) return false; - bool r = p.second->stream->enable(p.first); - if (r) Stream::enable(id); - return r; + auto p = _mapToOutput(id); + if (!p.second) return false; + bool r = p.second->stream->enable(p.first); + if (r) Stream::enable(id); + return r; } bool Muxer::enable(FrameID id, ftl::protocol::Channel channel) { - auto p = _mapToOutput(id); - if (!p.second) return false; - bool r = p.second->stream->enable(p.first, channel); - if (r) Stream::enable(id, channel); - return r; + auto p = _mapToOutput(id); + if (!p.second) return false; + bool r = p.second->stream->enable(p.first, channel); + if (r) Stream::enable(id, channel); + return r; } bool Muxer::enable(FrameID id, const ftl::protocol::ChannelSet &channels) { - auto p = _mapToOutput(id); - if (!p.second) return false; - bool r = p.second->stream->enable(p.first, channels); - if (r) Stream::enable(id, channels); - return r; + auto p = _mapToOutput(id); + if (!p.second) return false; + bool r = p.second->stream->enable(p.first, channels); + if (r) Stream::enable(id, channels); + return r; } void Muxer::setProperty(ftl::protocol::StreamProperty opt, std::any value) { - for (auto &s : streams_) { - s.stream->setProperty(opt, value); - } + for (auto &s : streams_) { + s.stream->setProperty(opt, value); + } } std::any Muxer::getProperty(ftl::protocol::StreamProperty opt) { - for (auto &s : streams_) { - if (s.stream->supportsProperty(opt)) return s.stream->getProperty(opt); - } - return 0; + for (auto &s : streams_) { + if (s.stream->supportsProperty(opt)) return s.stream->getProperty(opt); + } + return 0; } bool Muxer::supportsProperty(ftl::protocol::StreamProperty opt) { - for (auto &s : streams_) { - if (s.stream->supportsProperty(opt)) return true; - } - return false; + for (auto &s : streams_) { + if (s.stream->supportsProperty(opt)) return true; + } + return false; } StreamType Muxer::type() const { - StreamType t = StreamType::kUnknown; - for (const auto &s : streams_) { - const StreamType tt = s.stream->type(); - if (t == StreamType::kUnknown) t = tt; - else if (t != tt) t = StreamType::kMixed; - } - return t; + StreamType t = StreamType::kUnknown; + for (const auto &s : streams_) { + const StreamType tt = s.stream->type(); + if (t == StreamType::kUnknown) t = tt; + else if (t != tt) t = StreamType::kMixed; + } + return t; } diff --git a/src/streams/netstream.cpp b/src/streams/netstream.cpp index 128eebcb2ac1c1fcf45a579e98996275b212697e..a780bfc18a1fd4fc08a3f8ad628677438b4b07b4 100644 --- a/src/streams/netstream.cpp +++ b/src/streams/netstream.cpp @@ -1,5 +1,12 @@ +/** + * @file netstream.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + +#include <list> +#include <string> #include "netstream.hpp" -//#include "adaptive.hpp" #include <ftl/time.hpp> #include "packetMsgpack.hpp" @@ -26,6 +33,9 @@ using ftl::protocol::kAllFramesets; using ftl::protocol::StreamProperty; using std::string; using std::optional; +using std::chrono::time_point_cast; +using std::chrono::milliseconds; +using std::chrono::high_resolution_clock; std::atomic_size_t Net::req_bitrate__ = 0; std::atomic_size_t Net::tx_bitrate__ = 0; @@ -38,512 +48,520 @@ static std::list<std::string> net_streams; static SHARED_MUTEX stream_mutex; void Net::installRPC(ftl::net::Universe *net) { - net->bind("find_stream", [net](const std::string &uri) -> optional<ftl::UUID> { - DLOG(INFO) << "Request for stream: " << uri; - - ftl::URI u1(uri); - std::string base = u1.getBaseURI(); - - SHARED_LOCK(stream_mutex, lk); - for (const auto &s : net_streams) { - ftl::URI u2(s); - // Don't compare query string components. - if (base == u2.getBaseURI()) { - return net->id(); - } - } - return {}; - }); - - net->bind("list_streams", []() { - SHARED_LOCK(stream_mutex, lk); - return net_streams; - }); - - net->bind("enable_stream", [](const std::string &uri, unsigned int fsid, unsigned int fid) { - // Nothing to do here, used by web service - }); - - net->bind("add_stream", [](const std::string &uri) { - // TODO: Trigger some callback - }); - - // TODO: Call "list_streams" to get all available locally - // This call should be done on any Peer connection - // and perhaps periodically + net->bind("find_stream", [net](const std::string &uri) -> optional<ftl::UUID> { + DLOG(INFO) << "Request for stream: " << uri; + + ftl::URI u1(uri); + std::string base = u1.getBaseURI(); + + SHARED_LOCK(stream_mutex, lk); + for (const auto &s : net_streams) { + ftl::URI u2(s); + // Don't compare query string components. + if (base == u2.getBaseURI()) { + return std::reference_wrapper(net->id()); + } + } + return {}; + }); + + net->bind("list_streams", []() { + SHARED_LOCK(stream_mutex, lk); + return net_streams; + }); + + net->bind("enable_stream", [](const std::string &uri, unsigned int fsid, unsigned int fid) { + // Nothing to do here, used by web service + }); + + net->bind("add_stream", [](const std::string &uri) { + // TODO(Nick): Trigger some callback + }); + + // TODO(Nick): Call "list_streams" to get all available locally + // This call should be done on any Peer connection + // and perhaps periodically } Net::Net(const std::string &uri, ftl::net::Universe *net, bool host) : - net_(net), time_peer_(ftl::UUID(0)), uri_(uri), host_(host) { - - ftl::URI u(uri_); - if (!u.isValid() || !(u.getScheme() == ftl::URI::SCHEME_FTL)) { - error(Error::kBadURI, uri_); - throw FTL_Error("Bad stream URI"); - } - base_uri_ = u.getBaseURI(); - - if (host_) { - // Automatically set name - name_.resize(1024); - #ifdef WIN32 - DWORD size = name_.capacity(); - GetComputerName(name_.data(), &size); - #else - gethostname(name_.data(), name_.capacity()); - #endif - } else { - name_ = "No name"; - } + net_(net), time_peer_(ftl::UUID(0)), uri_(uri), host_(host) { + ftl::URI u(uri_); + if (!u.isValid() || !(u.getScheme() == ftl::URI::SCHEME_FTL)) { + error(Error::kBadURI, uri_); + throw FTL_Error("Bad stream URI"); + } + base_uri_ = u.getBaseURI(); + + if (host_) { + // Automatically set name + name_.resize(1024); + #ifdef WIN32 + DWORD size = name_.capacity(); + GetComputerName(name_.data(), &size); + #else + gethostname(name_.data(), name_.capacity()); + #endif + } else { + name_ = "No name"; + } } Net::~Net() { - end(); + end(); - // FIXME: Wait to ensure no net callbacks are active. - // Do something better than this - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // FIXME: Wait to ensure no net callbacks are active. + // Do something better than this + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } bool Net::post(const StreamPacket &spkt, const Packet &pkt) { - if (!active_) return false; - if (paused_) return true; - bool hasStale = false; - - // Cast to include msgpack methods - auto spkt_net = reinterpret_cast<const StreamPacketMSGPACK&>(spkt); - - // Version of packet without data but with msgpack methods - PacketMSGPACK pkt_strip; - pkt_strip.codec = pkt.codec; - pkt_strip.bitrate = pkt.bitrate; - pkt_strip.frame_count = pkt.frame_count; - pkt_strip.flags = pkt.flags; - - if (host_) { - SHARED_LOCK(mutex_,lk); - for (auto &client : clients_) { - // Strip packet data if channel is not wanted by client - const bool strip = int(spkt.channel) < 32 && pkt.data.size() > 0 && ((1 << int(spkt.channel)) & client.channels) == 0; - - try { - short pre_transmit_latency = short(ftl::time::get_time() - spkt.localTimestamp); - - // TODO: msgpack only once and broadcast. - // TODO: send in parallel and then wait on all futures? - // Or send non-blocking and wait - if (!net_->send(client.peerid, - base_uri_, - pre_transmit_latency, // Time since timestamp for tx - spkt_net, - (strip) ? pkt_strip : reinterpret_cast<const PacketMSGPACK&>(pkt))) { - - // Send failed so mark as client stream completed - client.txcount = client.txmax; - } else { - if (!strip && pkt.data.size() > 0) _checkTXRate(pkt.data.size(), 0, spkt.timestamp); - - // Count frame as completed only if last block and channel is 0 - // FIXME: This is unreliable, colour might not exist etc. - if (spkt_net.streamID == 0 && spkt.frame_number == 0 && spkt.channel == Channel::kColour) ++client.txcount; - } - } catch(...) { - client.txcount = client.txmax; - } - - if (client.txcount >= client.txmax) { - hasStale = true; - } - } - } else { - try { - short pre_transmit_latency = short(ftl::time::get_time() - spkt.localTimestamp); - - if (!net_->send(*peer_, - base_uri_, - pre_transmit_latency, // Time since timestamp for tx - spkt_net, - reinterpret_cast<const PacketMSGPACK&>(pkt))) { - - } - if (pkt.data.size() > 0) _checkTXRate(pkt.data.size(), 0, spkt.timestamp); - } catch(...) { - // TODO: Some disconnect error - return false; - } - } - - if (hasStale) _cleanUp(); - - hasPosted(spkt, pkt); - - return true; + if (!active_) return false; + if (paused_) return true; + bool hasStale = false; + + // Cast to include msgpack methods + auto spkt_net = reinterpret_cast<const StreamPacketMSGPACK&>(spkt); + + // Version of packet without data but with msgpack methods + PacketMSGPACK pkt_strip; + pkt_strip.codec = pkt.codec; + pkt_strip.bitrate = pkt.bitrate; + pkt_strip.frame_count = pkt.frame_count; + pkt_strip.flags = pkt.flags; + + if (host_) { + SHARED_LOCK(mutex_, lk); + for (auto &client : clients_) { + // Strip packet data if channel is not wanted by client + const bool strip = + static_cast<int>(spkt.channel) < 32 && pkt.data.size() > 0 + && ((1 << static_cast<int>(spkt.channel)) & client.channels) == 0; + + try { + int16_t pre_transmit_latency = int16_t(ftl::time::get_time() - spkt.localTimestamp); + + // TODO(Nick): msgpack only once and broadcast. + // TODO(Nick): send in parallel and then wait on all futures? + // Or send non-blocking and wait + if (!net_->send(client.peerid, + base_uri_, + pre_transmit_latency, // Time since timestamp for tx + spkt_net, + (strip) ? pkt_strip : reinterpret_cast<const PacketMSGPACK&>(pkt))) { + // Send failed so mark as client stream completed + client.txcount = client.txmax; + } else { + if (!strip && pkt.data.size() > 0) _checkTXRate(pkt.data.size(), 0, spkt.timestamp); + + // Count frame as completed only if last block and channel is 0 + // FIXME: This is unreliable, colour might not exist etc. + if (spkt_net.streamID == 0 && spkt.frame_number == 0 && spkt.channel == Channel::kColour) { + ++client.txcount; + } + } + } catch(...) { + client.txcount = client.txmax; + } + + if (client.txcount >= client.txmax) { + hasStale = true; + } + } + } else { + try { + int16_t pre_transmit_latency = int16_t(ftl::time::get_time() - spkt.localTimestamp); + + net_->send(*peer_, + base_uri_, + pre_transmit_latency, // Time since timestamp for tx + spkt_net, + reinterpret_cast<const PacketMSGPACK&>(pkt)); + + if (pkt.data.size() > 0) _checkTXRate(pkt.data.size(), 0, spkt.timestamp); + } catch(...) { + // TODO(Nick): Some disconnect error + return false; + } + } + + if (hasStale) _cleanUp(); + + hasPosted(spkt, pkt); + + return true; } -void Net::_processPacket(ftl::net::Peer *p, short ttimeoff, const StreamPacket &spkt_raw, const Packet &pkt) { - int64_t now = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count(); - - if (!active_) return; - - StreamPacket spkt = spkt_raw; - spkt.localTimestamp = now - int64_t(ttimeoff); - spkt.hint_capability = 0; - spkt.hint_source_total = 0; - spkt.version = 4; - if (p) spkt.hint_peerid = p->localID(); - - FrameID localFrame(spkt.streamID, spkt.frame_number); - - seen(localFrame, spkt.channel); - - if (paused_) return; - - // Manage recuring requests - if (!host_ && last_frame_ != spkt.timestamp) { - UNIQUE_LOCK(mutex_, lk); - if (last_frame_ != spkt.timestamp) { - //int tc = now - last_completion_; // Milliseconds since last frame completed - frame_time_ = spkt.timestamp - last_frame_; // Milliseconds per frame - last_completion_ = now; - bytes_received_ = 0; - last_frame_ = spkt.timestamp; - - lk.unlock(); - - // Are we close to reaching the end of our frames request? - if (tally_ <= 5) { - // Yes, so send new requests - // FIXME: Do this for all frames, or use tally be frame - //for (size_t i = 0; i < size(); ++i) { - const auto &sel = enabledChannels(localFrame); - - for (auto c : sel) { - _sendRequest(c, localFrame.frameset(), kAllFrames, frames_to_request_, 255); - } - //} - tally_ = frames_to_request_; - } else { - --tally_; - } - } - } - - bytes_received_ += pkt.data.size(); - //time_at_last_ = now; - - // If hosting and no data then it is a request for data - // Note: a non host can receive empty data, meaning data is available - // but that you did not request it - if (host_ && pkt.data.size() == 0 && (spkt.flags & ftl::protocol::kFlagRequest)) { - _processRequest(p, spkt, pkt); - } - - trigger(spkt, pkt); - if (pkt.data.size() > 0) _checkRXRate(pkt.data.size(), now-(spkt.timestamp+ttimeoff), spkt.timestamp); +void Net::_processPacket(ftl::net::Peer *p, int16_t ttimeoff, const StreamPacket &spkt_raw, const Packet &pkt) { + int64_t now = time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count(); + + if (!active_) return; + + StreamPacket spkt = spkt_raw; + spkt.localTimestamp = now - int64_t(ttimeoff); + spkt.hint_capability = 0; + spkt.hint_source_total = 0; + spkt.version = 4; + if (p) spkt.hint_peerid = p->localID(); + + FrameID localFrame(spkt.streamID, spkt.frame_number); + + seen(localFrame, spkt.channel); + + if (paused_) return; + + // Manage recuring requests + if (!host_ && last_frame_ != spkt.timestamp) { + UNIQUE_LOCK(mutex_, lk); + if (last_frame_ != spkt.timestamp) { + // int tc = now - last_completion_; // Milliseconds since last frame completed + frame_time_ = spkt.timestamp - last_frame_; // Milliseconds per frame + last_completion_ = now; + bytes_received_ = 0; + last_frame_ = spkt.timestamp; + + lk.unlock(); + + // Are we close to reaching the end of our frames request? + if (tally_ <= 5) { + // Yes, so send new requests + // FIXME: Do this for all frames, or use tally be frame + // for (size_t i = 0; i < size(); ++i) { + const auto &sel = enabledChannels(localFrame); + for (auto c : sel) { + _sendRequest(c, localFrame.frameset(), kAllFrames, frames_to_request_, 255); + } + //} + tally_ = frames_to_request_; + } else { + --tally_; + } + } + } + + bytes_received_ += pkt.data.size(); + // time_at_last_ = now; + + // If hosting and no data then it is a request for data + // Note: a non host can receive empty data, meaning data is available + // but that you did not request it + if (host_ && pkt.data.size() == 0 && (spkt.flags & ftl::protocol::kFlagRequest)) { + _processRequest(p, &spkt, pkt); + } + + trigger(spkt, pkt); + if (pkt.data.size() > 0) _checkRXRate(pkt.data.size(), now-(spkt.timestamp+ttimeoff), spkt.timestamp); } void Net::inject(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt) { - _processPacket(nullptr, 0, spkt, pkt); + _processPacket(nullptr, 0, spkt, pkt); } bool Net::begin() { - if (active_) return true; - - if (net_->isBound(base_uri_)) { - error(Error::kURIAlreadyExists, std::string("Stream already exists: ") + uri_); - active_ = false; - return false; - } - - // FIXME: Potential race between above check and new binding - - // Add the RPC handler for the URI - net_->bind(base_uri_, [this](ftl::net::Peer &p, short ttimeoff, const StreamPacketMSGPACK &spkt_raw, const PacketMSGPACK &pkt) { - _processPacket(&p, ttimeoff, spkt_raw, pkt); - }); - - if (host_) { - DLOG(INFO) << "Hosting stream: " << uri_; - - { - // Add to list of available streams - UNIQUE_LOCK(stream_mutex, lk); - net_streams.push_back(uri_); - } - - net_->broadcast("add_stream", uri_); - active_ = true; - - } else { - tally_ = frames_to_request_; - active_ = true; - } - - return true; + if (active_) return true; + + if (net_->isBound(base_uri_)) { + error(Error::kURIAlreadyExists, std::string("Stream already exists: ") + uri_); + active_ = false; + return false; + } + + // FIXME: Potential race between above check and new binding + + // Add the RPC handler for the URI + net_->bind(base_uri_, [this]( + ftl::net::Peer &p, + int16_t ttimeoff, + const StreamPacketMSGPACK &spkt_raw, + const PacketMSGPACK &pkt) { + _processPacket(&p, ttimeoff, spkt_raw, pkt); + }); + + if (host_) { + DLOG(INFO) << "Hosting stream: " << uri_; + + { + // Add to list of available streams + UNIQUE_LOCK(stream_mutex, lk); + net_streams.push_back(uri_); + } + + net_->broadcast("add_stream", uri_); + active_ = true; + + } else { + tally_ = frames_to_request_; + active_ = true; + } + + return true; } void Net::refresh() { - Stream::refresh(); - - UNIQUE_LOCK(mutex_, lk); - - for (const auto &i : enabled()) { - auto sel = enabledChannels(i); - - for (auto c : sel) { - _sendRequest(c, i.frameset(), kAllFrames, frames_to_request_, 255, true); - } - } - tally_ = frames_to_request_; + Stream::refresh(); + + UNIQUE_LOCK(mutex_, lk); + + for (const auto &i : enabled()) { + auto sel = enabledChannels(i); + + for (auto c : sel) { + _sendRequest(c, i.frameset(), kAllFrames, frames_to_request_, 255, true); + } + } + tally_ = frames_to_request_; } void Net::reset() { - Stream::reset(); + Stream::reset(); } bool Net::_enable(FrameID id) { - if (host_) { return false; } - if (enabled(id)) return true; - - // not hosting, try to find peer now - // First find non-proxy version, then check for proxy version if no match - auto p = net_->findOne<ftl::UUID>("find_stream", uri_); - - if (p) { - peer_ = *p; - } else { - // use webservice (if connected) - auto ws = net_->getWebService(); - if (ws) { - peer_ = ws->id(); - } else { - error(Error::kURIDoesNotExist, std::string("Stream not found: ") + uri_); - return false; - } - } - - // TODO: check return value - net_->send(*peer_, "enable_stream", uri_, id.frameset(), id.source()); - return true; + if (host_) { return false; } + if (enabled(id)) return true; + + // not hosting, try to find peer now + // First find non-proxy version, then check for proxy version if no match + auto p = net_->findOne<ftl::UUID>("find_stream", uri_); + + if (p) { + peer_ = *p; + } else { + // use webservice (if connected) + auto ws = net_->getWebService(); + if (ws) { + peer_ = ws->id(); + } else { + error(Error::kURIDoesNotExist, std::string("Stream not found: ") + uri_); + return false; + } + } + + // TODO(Nick): check return value + net_->send(*peer_, "enable_stream", uri_, id.frameset(), id.source()); + return true; } bool Net::enable(FrameID id) { - if (host_) { return false; } - if (!_enable(id)) return false; - if (!Stream::enable(id)) return false; - _sendRequest(Channel::kColour, id.frameset(), kAllFrames, kFramesToRequest, 255, true); + if (host_) { return false; } + if (!_enable(id)) return false; + if (!Stream::enable(id)) return false; + _sendRequest(Channel::kColour, id.frameset(), kAllFrames, kFramesToRequest, 255, true); - return true; + return true; } bool Net::enable(FrameID id, Channel c) { - if (host_) { return false; } - if (!_enable(id)) return false; - if (!Stream::enable(id, c)) return false; - _sendRequest(c, id.frameset(), kAllFrames, kFramesToRequest, 255, true); - return true; + if (host_) { return false; } + if (!_enable(id)) return false; + if (!Stream::enable(id, c)) return false; + _sendRequest(c, id.frameset(), kAllFrames, kFramesToRequest, 255, true); + return true; } bool Net::enable(FrameID id, const ChannelSet &channels) { - if (host_) { return false; } - if (!_enable(id)) return false; - if (!Stream::enable(id, channels)) return false; - for (auto c : channels) { - _sendRequest(c, id.frameset(), kAllFrames, kFramesToRequest, 255, true); - } - return true; + if (host_) { return false; } + if (!_enable(id)) return false; + if (!Stream::enable(id, channels)) return false; + for (auto c : channels) { + _sendRequest(c, id.frameset(), kAllFrames, kFramesToRequest, 255, true); + } + return true; } bool Net::_sendRequest(Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate, bool doreset) { - if (!active_ || host_) return false; - - PacketMSGPACK pkt = { - Codec::kAny, // TODO: Allow specific codec requests - 0, - count, - bitrate_, - 0 - }; - - uint8_t sflags = ftl::protocol::kFlagRequest; - if (doreset) sflags |= ftl::protocol::kFlagReset; - - StreamPacketMSGPACK spkt = { - 5, - ftl::time::get_time(), - frameset, - frames, - c, - sflags, - 0, - 0, - 0 - }; - - net_->send(*peer_, base_uri_, (short)0, spkt, pkt); - hasPosted(spkt, pkt); - return true; + if (!active_ || host_) return false; + + PacketMSGPACK pkt = { + Codec::kAny, // TODO(Nick): Allow specific codec requests + 0, + count, + bitrate_, + 0 + }; + + uint8_t sflags = ftl::protocol::kFlagRequest; + if (doreset) sflags |= ftl::protocol::kFlagReset; + + StreamPacketMSGPACK spkt = { + 5, + ftl::time::get_time(), + frameset, + frames, + c, + sflags, + 0, + 0, + 0 + }; + + net_->send(*peer_, base_uri_, (int16_t)0, spkt, pkt); + hasPosted(spkt, pkt); + return true; } void Net::_cleanUp() { - UNIQUE_LOCK(mutex_,lk); - for (auto i=clients_.begin(); i!=clients_.end(); ++i) { - auto &client = *i; - if (client.txcount >= client.txmax) { - if (client.peerid == time_peer_) { - time_peer_ = ftl::UUID(0); - } - DLOG(INFO) << "Remove peer: " << client.peerid.to_string(); - i = clients_.erase(i); - } - } + UNIQUE_LOCK(mutex_, lk); + for (auto i = clients_.begin(); i != clients_.end(); ++i) { + auto &client = *i; + if (client.txcount >= client.txmax) { + if (client.peerid == time_peer_) { + time_peer_ = ftl::UUID(0); + } + DLOG(INFO) << "Remove peer: " << client.peerid.to_string(); + i = clients_.erase(i); + } + } } /* Packets for specific framesets, frames and channels are requested in * batches (max 255 unique frames by timestamp). Requests are in the form * of packets that match the request except the data component is empty. */ -bool Net::_processRequest(ftl::net::Peer *p, StreamPacket &spkt, const Packet &pkt) { - bool found = false; - DLOG(INFO) << "processing request: " << int(spkt.streamID) << ", " << int(spkt.channel); - - if (p) { - SHARED_LOCK(mutex_,lk); - // Does the client already exist - for (auto &c : clients_) { - if (c.peerid == p->id()) { - // Yes, so reset internal request counters - c.txcount = 0; - c.txmax = static_cast<int>(pkt.frame_count); - if (int(spkt.channel) < 32) c.channels |= 1 << int(spkt.channel); - found = true; - // break; - } - } - } - - // No existing client, so add a new one. - if (p && !found) { - { - UNIQUE_LOCK(mutex_,lk); - - auto &client = clients_.emplace_back(); - client.peerid = p->id(); - client.quality = 255; // TODO: Use quality given in packet - client.txcount = 0; - client.txmax = static_cast<int>(pkt.frame_count); - if (int(spkt.channel) < 32) client.channels |= 1 << int(spkt.channel); - } - - spkt.hint_capability |= ftl::protocol::kStreamCap_NewConnection; - - try { - connect_cb_.trigger(p); - } catch (const ftl::exception &e) { - LOG(ERROR) << "Exception in stream connect callback: " << e.what(); - } - } - - ftl::protocol::Request req; - req.bitrate = pkt.bitrate; - req.channel = spkt.channel; - req.id = FrameID(spkt.streamID, spkt.frame_number); - req.count = pkt.frame_count; - req.codec = pkt.codec; - request(req); - - return false; +bool Net::_processRequest(ftl::net::Peer *p, StreamPacket *spkt, const Packet &pkt) { + bool found = false; + DLOG(INFO) << "processing request: " << int(spkt->streamID) << ", " << int(spkt->channel); + + if (p) { + SHARED_LOCK(mutex_, lk); + // Does the client already exist + for (auto &c : clients_) { + if (c.peerid == p->id()) { + // Yes, so reset internal request counters + c.txcount = 0; + c.txmax = static_cast<int>(pkt.frame_count); + if (static_cast<int>(spkt->channel) < 32) { + c.channels |= 1 << static_cast<int>(spkt->channel); + } + found = true; + // break; + } + } + } + + // No existing client, so add a new one. + if (p && !found) { + { + UNIQUE_LOCK(mutex_, lk); + + auto &client = clients_.emplace_back(); + client.peerid = p->id(); + client.quality = 255; // TODO(Nick): Use quality given in packet + client.txcount = 0; + client.txmax = static_cast<int>(pkt.frame_count); + if (static_cast<int>(spkt->channel) < 32) { + client.channels |= 1 << static_cast<int>(spkt->channel); + } + } + + spkt->hint_capability |= ftl::protocol::kStreamCap_NewConnection; + + try { + connect_cb_.trigger(p); + } catch (const ftl::exception &e) { + LOG(ERROR) << "Exception in stream connect callback: " << e.what(); + } + } + + ftl::protocol::Request req; + req.bitrate = pkt.bitrate; + req.channel = spkt->channel; + req.id = FrameID(spkt->streamID, spkt->frame_number); + req.count = pkt.frame_count; + req.codec = pkt.codec; + request(req); + + return false; } void Net::_checkRXRate(size_t rx_size, int64_t rx_latency, int64_t ts) { - req_bitrate__ += rx_size * 8; - rx_sample_count__ += 1; + req_bitrate__ += rx_size * 8; + rx_sample_count__ += 1; } void Net::_checkTXRate(size_t tx_size, int64_t tx_latency, int64_t ts) { - tx_bitrate__ += tx_size * 8; - tx_sample_count__ += 1; + tx_bitrate__ += tx_size * 8; + tx_sample_count__ += 1; } NetStats Net::getStatistics() { - int64_t ts = ftl::time::get_time(); - UNIQUE_LOCK(msg_mtx__,lk); - const float r = (float(req_bitrate__) / float(ts - last_msg__) * 1000.0f / 1048576.0f); - const float t = (float(tx_bitrate__) / float(ts - last_msg__) * 1000.0f / 1048576.0f); - last_msg__ = ts; - req_bitrate__ = 0; - tx_bitrate__ = 0; - rx_sample_count__ = 0; - tx_sample_count__ = 0; - return {r, t}; + int64_t ts = ftl::time::get_time(); + UNIQUE_LOCK(msg_mtx__, lk); + const float r = (static_cast<float>(req_bitrate__) / static_cast<float>(ts - last_msg__) * 1000.0f / 1048576.0f); + const float t = (static_cast<float>(tx_bitrate__) / static_cast<float>(ts - last_msg__) * 1000.0f / 1048576.0f); + last_msg__ = ts; + req_bitrate__ = 0; + tx_bitrate__ = 0; + rx_sample_count__ = 0; + tx_sample_count__ = 0; + return {r, t}; } bool Net::end() { - if (!active_) return false; + if (!active_) return false; - { - UNIQUE_LOCK(stream_mutex, lk); - auto i = std::find(net_streams.begin(), net_streams.end(), uri_); - if (i != net_streams.end()) net_streams.erase(i); - } + { + UNIQUE_LOCK(stream_mutex, lk); + auto i = std::find(net_streams.begin(), net_streams.end(), uri_); + if (i != net_streams.end()) net_streams.erase(i); + } - active_ = false; - net_->unbind(base_uri_); - return true; + active_ = false; + net_->unbind(base_uri_); + return true; } bool Net::active() { - return active_; + return active_; } void Net::setProperty(ftl::protocol::StreamProperty opt, std::any value) { - switch (opt) { - case StreamProperty::kBitrate : - case StreamProperty::kMaxBitrate : bitrate_ = std::any_cast<int>(value); break; - case StreamProperty::kPaused : paused_ = std::any_cast<bool>(value); break; - case StreamProperty::kName : name_ = std::any_cast<std::string>(value); break; - case StreamProperty::kObservers : - case StreamProperty::kBytesSent : - case StreamProperty::kBytesReceived : - case StreamProperty::kLatency : - case StreamProperty::kFrameRate : - case StreamProperty::kURI : throw FTL_Error("Readonly property"); - default : throw FTL_Error("Unsupported property"); - } + switch (opt) { + case StreamProperty::kBitrate : + case StreamProperty::kMaxBitrate : bitrate_ = std::any_cast<int>(value); break; + case StreamProperty::kPaused : paused_ = std::any_cast<bool>(value); break; + case StreamProperty::kName : name_ = std::any_cast<std::string>(value); break; + case StreamProperty::kObservers : + case StreamProperty::kBytesSent : + case StreamProperty::kBytesReceived : + case StreamProperty::kLatency : + case StreamProperty::kFrameRate : + case StreamProperty::kURI : throw FTL_Error("Readonly property"); + default : throw FTL_Error("Unsupported property"); + } } std::any Net::getProperty(ftl::protocol::StreamProperty opt) { - switch (opt) { - case StreamProperty::kBitrate : - case StreamProperty::kMaxBitrate : return bitrate_; - case StreamProperty::kObservers : return clients_.size(); - case StreamProperty::kURI : return base_uri_; - case StreamProperty::kPaused : return paused_; - case StreamProperty::kBytesSent : return 0; - case StreamProperty::kBytesReceived : return int64_t(bytes_received_); - case StreamProperty::kFrameRate : return (frame_time_ > 0) ? 1000 / frame_time_ : 0; - case StreamProperty::kLatency : return 0; - case StreamProperty::kName : return name_; - default : throw FTL_Error("Unsupported property"); - } + switch (opt) { + case StreamProperty::kBitrate : + case StreamProperty::kMaxBitrate : return bitrate_; + case StreamProperty::kObservers : return clients_.size(); + case StreamProperty::kURI : return base_uri_; + case StreamProperty::kPaused : return paused_; + case StreamProperty::kBytesSent : return 0; + case StreamProperty::kBytesReceived : return int64_t(bytes_received_); + case StreamProperty::kFrameRate : return (frame_time_ > 0) ? 1000 / frame_time_ : 0; + case StreamProperty::kLatency : return 0; + case StreamProperty::kName : return name_; + default : throw FTL_Error("Unsupported property"); + } } bool Net::supportsProperty(ftl::protocol::StreamProperty opt) { - switch (opt) { - case StreamProperty::kBitrate : - case StreamProperty::kMaxBitrate : - case StreamProperty::kObservers : - case StreamProperty::kPaused : - case StreamProperty::kBytesSent : - case StreamProperty::kBytesReceived : - case StreamProperty::kLatency : - case StreamProperty::kFrameRate : - case StreamProperty::kName : - case StreamProperty::kURI : return true; - default : return false; - } + switch (opt) { + case StreamProperty::kBitrate : + case StreamProperty::kMaxBitrate : + case StreamProperty::kObservers : + case StreamProperty::kPaused : + case StreamProperty::kBytesSent : + case StreamProperty::kBytesReceived : + case StreamProperty::kLatency : + case StreamProperty::kFrameRate : + case StreamProperty::kName : + case StreamProperty::kURI : return true; + default : return false; + } } ftl::protocol::StreamType Net::type() const { - return ftl::protocol::StreamType::kLive; + return ftl::protocol::StreamType::kLive; } diff --git a/src/streams/netstream.hpp b/src/streams/netstream.hpp index d311f9d332962dc47d3c9fa3b7e050709fb6398f..c861077a68821ccdb3f52ed71e4116573b570676 100644 --- a/src/streams/netstream.hpp +++ b/src/streams/netstream.hpp @@ -1,22 +1,29 @@ +/** + * @file netstream.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once +#include <string> +#include <list> #include "../universe.hpp" #include <ftl/threads.hpp> #include <ftl/protocol/packet.hpp> #include <ftl/protocol/streams.hpp> #include <ftl/handle.hpp> -#include <string> namespace ftl { namespace protocol { namespace detail { struct StreamClient { - ftl::UUID peerid; - std::atomic<int> txcount; // Frames sent since last request - int txmax; // Frames to send in request - std::atomic<uint32_t> channels; // A channel mask, those that have been requested - uint8_t quality; + ftl::UUID peerid; + std::atomic<int> txcount; // Frames sent since last request + int txmax; // Frames to send in request + std::atomic<uint32_t> channels; // A channel mask, those that have been requested + uint8_t quality; }; } @@ -26,8 +33,8 @@ struct StreamClient { static const int kMaxFrames = 100; struct NetStats { - float rxRate; - float txRate; + float rxRate; + float txRate; }; /** @@ -36,97 +43,103 @@ struct NetStats { * Each packet post is forwarded to each connected client that is still active. */ class Net : public Stream { - public: - Net(const std::string &uri, ftl::net::Universe *net, bool host=false); - virtual ~Net(); - - bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; - - bool begin() override; - bool end() override; - bool active() override; - - bool enable(FrameID id) override; - bool enable(FrameID id, ftl::protocol::Channel c) override; - bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; - - void reset() override; - void refresh() override; - - void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; - std::any getProperty(ftl::protocol::StreamProperty opt) override; - bool supportsProperty(ftl::protocol::StreamProperty opt) override; - StreamType type() const override; - - inline const ftl::UUID &getPeer() const { - if (host_) { throw FTL_Error("Net::getPeer() not possible, hosting stream"); } - if (!peer_){ throw FTL_Error("steram::Net has no valid Peer. Not found earlier?"); } - return *peer_; - } - - inline ftl::Handle onClientConnect(const std::function<bool(ftl::net::Peer*)> &cb) { return connect_cb_.on(cb); } - - /** - * Return the average bitrate of all streams since the last call to this - * function. Units are Mbps. - */ - static NetStats getStatistics(); - - static void installRPC(ftl::net::Universe *net); - - static constexpr int kFramesToRequest = 30; - - // Unit test support - virtual void hasPosted(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) {} - void inject(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &); - -private: - SHARED_MUTEX mutex_; - bool active_ = false; - ftl::net::Universe *net_; - int64_t clock_adjust_ = 0; - ftl::UUID time_peer_; - std::optional<ftl::UUID> peer_; - int64_t last_frame_ = 0; - int64_t last_ping_ = 0; - int64_t frame_time_ = 0; - std::string uri_; - std::string base_uri_; - const bool host_; - int tally_ = 0; - std::array<std::atomic<int>,32> reqtally_ = {0}; - ftl::protocol::ChannelSet last_selected_; - uint8_t bitrate_ = 200; - std::atomic_int64_t bytes_received_ = 0; - int64_t last_completion_ = 0; - int64_t time_at_last_ = 0; - float required_bps_ = 0.0f; - float actual_bps_ = 0.0f; - bool paused_ = false; - int frames_to_request_ = kFramesToRequest; - std::string name_; - - ftl::Handler<ftl::net::Peer*> connect_cb_; - - uint32_t local_fsid_ = 0; - - static std::atomic_size_t req_bitrate__; - static std::atomic_size_t tx_bitrate__; - static std::atomic_size_t rx_sample_count__; - static std::atomic_size_t tx_sample_count__; - static int64_t last_msg__; - static MUTEX msg_mtx__; - - std::list<detail::StreamClient> clients_; - - bool _enable(FrameID id); - bool _processRequest(ftl::net::Peer *p, ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt); - void _checkRXRate(size_t rx_size, int64_t rx_latency, int64_t ts); - void _checkTXRate(size_t tx_size, int64_t tx_latency, int64_t ts); - bool _sendRequest(ftl::protocol::Channel c, uint8_t frameset, uint8_t frames, uint8_t count, uint8_t bitrate, bool doreset=false); - void _cleanUp(); - void _processPacket(ftl::net::Peer *p, short ttimeoff, const StreamPacket &spkt_raw, const Packet &pkt); + public: + Net(const std::string &uri, ftl::net::Universe *net, bool host = false); + virtual ~Net(); + + bool post(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) override; + + bool begin() override; + bool end() override; + bool active() override; + + bool enable(FrameID id) override; + bool enable(FrameID id, ftl::protocol::Channel c) override; + bool enable(FrameID id, const ftl::protocol::ChannelSet &channels) override; + + void reset() override; + void refresh() override; + + void setProperty(ftl::protocol::StreamProperty opt, std::any value) override; + std::any getProperty(ftl::protocol::StreamProperty opt) override; + bool supportsProperty(ftl::protocol::StreamProperty opt) override; + StreamType type() const override; + + inline const ftl::UUID &getPeer() const { + if (host_) { throw FTL_Error("Net::getPeer() not possible, hosting stream"); } + if (!peer_) { throw FTL_Error("steram::Net has no valid Peer. Not found earlier?"); } + return *peer_; + } + + inline ftl::Handle onClientConnect(const std::function<bool(ftl::net::Peer*)> &cb) { return connect_cb_.on(cb); } + + /** + * Return the average bitrate of all streams since the last call to this + * function. Units are Mbps. + */ + static NetStats getStatistics(); + + static void installRPC(ftl::net::Universe *net); + + static constexpr int kFramesToRequest = 30; + + // Unit test support + virtual void hasPosted(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &) {} + void inject(const ftl::protocol::StreamPacket &, const ftl::protocol::Packet &); + + private: + SHARED_MUTEX mutex_; + bool active_ = false; + ftl::net::Universe *net_; + int64_t clock_adjust_ = 0; + ftl::UUID time_peer_; + std::optional<ftl::UUID> peer_; + int64_t last_frame_ = 0; + int64_t last_ping_ = 0; + int64_t frame_time_ = 0; + std::string uri_; + std::string base_uri_; + const bool host_; + int tally_ = 0; + std::array<std::atomic<int>, 32> reqtally_ = {0}; + ftl::protocol::ChannelSet last_selected_; + uint8_t bitrate_ = 200; + std::atomic_int64_t bytes_received_ = 0; + int64_t last_completion_ = 0; + int64_t time_at_last_ = 0; + float required_bps_ = 0.0f; + float actual_bps_ = 0.0f; + bool paused_ = false; + int frames_to_request_ = kFramesToRequest; + std::string name_; + + ftl::Handler<ftl::net::Peer*> connect_cb_; + + uint32_t local_fsid_ = 0; + + static std::atomic_size_t req_bitrate__; + static std::atomic_size_t tx_bitrate__; + static std::atomic_size_t rx_sample_count__; + static std::atomic_size_t tx_sample_count__; + static int64_t last_msg__; + static MUTEX msg_mtx__; + + std::list<detail::StreamClient> clients_; + + bool _enable(FrameID id); + bool _processRequest(ftl::net::Peer *p, ftl::protocol::StreamPacket *spkt, const ftl::protocol::Packet &pkt); + void _checkRXRate(size_t rx_size, int64_t rx_latency, int64_t ts); + void _checkTXRate(size_t tx_size, int64_t tx_latency, int64_t ts); + bool _sendRequest( + ftl::protocol::Channel c, + uint8_t frameset, + uint8_t frames, + uint8_t count, + uint8_t bitrate, + bool doreset = false); + void _cleanUp(); + void _processPacket(ftl::net::Peer *p, int16_t ttimeoff, const StreamPacket &spkt_raw, const Packet &pkt); }; -} -} +} // namespace protocol +} // namespace ftl diff --git a/src/streams/packetMsgpack.hpp b/src/streams/packetMsgpack.hpp index 79f577cb8c56cc5c588d1393a054f27348331a2c..f8aca04a05a7406c4c610b62868397a537d93ef1 100644 --- a/src/streams/packetMsgpack.hpp +++ b/src/streams/packetMsgpack.hpp @@ -1,3 +1,9 @@ +/** + * @file packetMsgpack.hpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #pragma once #include <ftl/protocol/packet.hpp> @@ -20,5 +26,5 @@ struct PacketMSGPACK : ftl::protocol::Packet { static_assert(sizeof(StreamPacketMSGPACK) == sizeof(StreamPacket)); static_assert(sizeof(PacketMSGPACK) == sizeof(Packet)); -} -} +} // namespace protocol +} // namespace ftl diff --git a/src/streams/streams.cpp b/src/streams/streams.cpp index bf12a13c415526135d9a886d815d7ee1687ff3b2..bf5ba8c14a2f828067b152fab98526420bb8ef82 100644 --- a/src/streams/streams.cpp +++ b/src/streams/streams.cpp @@ -1,3 +1,9 @@ +/** + * @file streams.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <ftl/protocol/streams.hpp> using ftl::protocol::Stream; @@ -6,143 +12,141 @@ using ftl::protocol::ChannelSet; using ftl::protocol::FrameID; std::string Stream::name() const { - return "Unknown"; + return "Unknown"; } bool Stream::available(FrameID id) const { - SHARED_LOCK(mtx_, lk); - return state_.count(id) > 0; + SHARED_LOCK(mtx_, lk); + return state_.count(id) > 0; } bool Stream::available(FrameID id, Channel channel) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - return it->second.available.count(channel) > 0; - } - return false; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + return it->second.available.count(channel) > 0; + } + return false; } bool Stream::available(FrameID id, ChannelSet channels) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - const auto &set = it->second.available; - for (auto channel : channels) { - if (set.count(channel) == 0) return false; - } - return true; - } - return false; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + const auto &set = it->second.available; + for (auto channel : channels) { + if (set.count(channel) == 0) return false; + } + return true; + } + return false; } ftl::protocol::ChannelSet Stream::channels(FrameID id) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - return it->second.available; - } - return {}; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + return it->second.available; + } + return {}; } std::unordered_set<FrameID> Stream::frames() const { - SHARED_LOCK(mtx_, lk); - std::unordered_set<FrameID> result; - for (const auto &s : state_) { - result.insert(FrameID(s.first)); - } - return result; + SHARED_LOCK(mtx_, lk); + std::unordered_set<FrameID> result; + for (const auto &s : state_) { + result.insert(FrameID(s.first)); + } + return result; } std::unordered_set<FrameID> Stream::enabled() const { - SHARED_LOCK(mtx_, lk); - std::unordered_set<FrameID> result; - for (const auto &s : state_) { - if (s.second.enabled) { - result.emplace(s.first); - } - } - return result; + SHARED_LOCK(mtx_, lk); + std::unordered_set<FrameID> result; + for (const auto &s : state_) { + if (s.second.enabled) { + result.emplace(s.first); + } + } + return result; } bool Stream::enabled(FrameID id) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - return it->second.enabled; - } - return false; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + return it->second.enabled; + } + return false; } bool Stream::enabled(FrameID id, ftl::protocol::Channel channel) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - return it->second.selected.count(channel) > 0; - } - return false; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + return it->second.selected.count(channel) > 0; + } + return false; } ftl::protocol::ChannelSet Stream::enabledChannels(FrameID id) const { - SHARED_LOCK(mtx_, lk); - auto it = state_.find(id); - if (it != state_.end()) { - return it->second.selected; - } - return {}; + SHARED_LOCK(mtx_, lk); + auto it = state_.find(id); + if (it != state_.end()) { + return it->second.selected; + } + return {}; } bool Stream::enable(FrameID id) { - UNIQUE_LOCK(mtx_, lk); - auto &p = state_[id]; - p.enabled = true; - return true; + UNIQUE_LOCK(mtx_, lk); + auto &p = state_[id]; + p.enabled = true; + return true; } bool Stream::enable(FrameID id, ftl::protocol::Channel channel) { - UNIQUE_LOCK(mtx_, lk); - auto &p = state_[id]; - p.enabled = true; - p.selected.insert(channel); - return true; + UNIQUE_LOCK(mtx_, lk); + auto &p = state_[id]; + p.enabled = true; + p.selected.insert(channel); + return true; } bool Stream::enable(FrameID id, const ftl::protocol::ChannelSet &channels) { - UNIQUE_LOCK(mtx_, lk); - auto &p = state_[id]; - p.enabled = true; - p.selected.insert(channels.begin(), channels.end()); - return true; + UNIQUE_LOCK(mtx_, lk); + auto &p = state_[id]; + p.enabled = true; + p.selected.insert(channels.begin(), channels.end()); + return true; } void Stream::reset() { - UNIQUE_LOCK(mtx_, lk); - state_.clear(); + UNIQUE_LOCK(mtx_, lk); + state_.clear(); } -void Stream::refresh() { - -} +void Stream::refresh() {} void Stream::trigger(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt) { - cb_.trigger(spkt, pkt); + cb_.trigger(spkt, pkt); } void Stream::seen(FrameID id, ftl::protocol::Channel channel) { - if (!available(id, channel)) { - { - UNIQUE_LOCK(mtx_, lk); - auto &p = state_[id]; - p.available.insert(channel); - } - avail_cb_.trigger(id, channel); - } + if (!available(id, channel)) { + { + UNIQUE_LOCK(mtx_, lk); + auto &p = state_[id]; + p.available.insert(channel); + } + avail_cb_.trigger(id, channel); + } } void Stream::request(const ftl::protocol::Request &req) { - request_cb_.trigger(req); + request_cb_.trigger(req); } void Stream::error(ftl::protocol::Error err, const std::string &str) { - error_cb_.trigger(err, str); + error_cb_.trigger(err, str); } diff --git a/src/time.cpp b/src/time.cpp index 772a7036dedbf01f64d0ed5c7e041fe871abf47c..27f496b45465082cfd4371adb506d041f7b581fa 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -1,24 +1,32 @@ -#include <ftl/time.hpp> +/** + * @file time.cpp + * @copyright Copyright (c) 2022 University of Turku, MIT License + * @author Nicolas Pope + */ + #include <chrono> +#include <ftl/time.hpp> using std::chrono::time_point_cast; using std::chrono::milliseconds; +using std::chrono::microseconds; using std::chrono::high_resolution_clock; static int64_t clock_adjust = 0; int64_t ftl::time::get_time() { - return time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count()+clock_adjust; + return time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count()+clock_adjust; } int64_t ftl::time::get_time_micro() { - return time_point_cast<std::chrono::microseconds>(high_resolution_clock::now()).time_since_epoch().count()+(clock_adjust*1000); + return time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count()+(clock_adjust*1000); } double ftl::time::get_time_seconds() { - return time_point_cast<std::chrono::microseconds>(high_resolution_clock::now()).time_since_epoch().count() / 1000000.0 + (static_cast<double>(clock_adjust) / 1000.0); + return time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count() / 1000000.0 + + (static_cast<double>(clock_adjust) / 1000.0); } void ftl::time::setClockAdjustment(int64_t ms) { - clock_adjust += ms; + clock_adjust += ms; } diff --git a/src/universe.cpp b/src/universe.cpp index ba6fc3b856e24d86fa893c2101ddf1fcbf31c1d5..26356e78b99f1e1c40d5b9fb1325e86eb4c4b5ca 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -4,10 +4,13 @@ * @author Nicolas Pope */ -#include "universe.hpp" -#include "socketImpl.hpp" #include <chrono> +#include <utility> #include <algorithm> +#include <memory> +#include <unordered_map> +#include "universe.hpp" +#include "socketImpl.hpp" #define LOGURU_REPLACE_GLOG 1 #include <ftl/lib/loguru.hpp> @@ -42,606 +45,596 @@ using ftl::protocol::NodeStatus; using ftl::protocol::NodeType; using ftl::net::internal::SocketServer; using ftl::net::internal::Server_TCP; +using std::chrono::milliseconds; namespace ftl { namespace net { std::unique_ptr<SocketServer> create_listener(const ftl::URI &uri) { - if (uri.getProtocol() == ftl::URI::scheme_t::SCHEME_TCP) { - return std::make_unique<Server_TCP>(uri.getHost(), uri.getPort()); - } - if (uri.getProtocol() == ftl::URI::scheme_t::SCHEME_WS) { - throw FTL_Error("WebSocket listener not implemented"); - } - return nullptr; + if (uri.getProtocol() == ftl::URI::scheme_t::SCHEME_TCP) { + return std::make_unique<Server_TCP>(uri.getHost(), uri.getPort()); + } + if (uri.getProtocol() == ftl::URI::scheme_t::SCHEME_WS) { + throw FTL_Error("WebSocket listener not implemented"); + } + return nullptr; } struct NetImplDetail { - //fd_set sfderror_; - //fd_set sfdread_; - std::vector<pollfd> pollfds; - std::unordered_map<int,size_t> idMap; + std::vector<pollfd> pollfds; + std::unordered_map<int, size_t> idMap; }; -} -} +} // namespace net +} // namespace ftl -// TODO: move to ServerSocket and ClientSocket +// TODO(Seb): move to ServerSocket and ClientSocket // Defaults, should be changed in config -#define TCP_SEND_BUFFER_SIZE (1024*1024) -#define TCP_RECEIVE_BUFFER_SIZE (1024*1024) // Perhaps try 24K? -#define WS_SEND_BUFFER_SIZE (1024*1024) -#define WS_RECEIVE_BUFFER_SIZE (62*1024) +#define TCP_SEND_BUFFER_SIZE (1024*1024) +#define TCP_RECEIVE_BUFFER_SIZE (1024*1024) // Perhaps try 24K? +#define WS_SEND_BUFFER_SIZE (1024*1024) +#define WS_RECEIVE_BUFFER_SIZE (62*1024) std::shared_ptr<Universe> Universe::instance_ = nullptr; Universe::Universe() : - active_(true), - this_peer(ftl::protocol::id), - impl_(new ftl::net::NetImplDetail()), - peers_(10), - phase_(0), - periodic_time_(1.0), - reconnect_attempts_(5), - thread_(Universe::__start, this) { - - _installBindings(); - - // Add an idle timer job to garbage collect peer objects - // Note: Important to be a timer job to ensure no other timer jobs are - // using the object. - // FIXME: Replace use of timer. - /*garbage_timer_ = ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) { - if (garbage_.size() > 0) { - UNIQUE_LOCK(net_mutex_,lk); - if (ftl::pool.n_idle() == ftl::pool.size()) { - if (garbage_.size() > 0) LOG(1) << "Garbage collection"; - while (garbage_.size() > 0) { - delete garbage_.front(); - garbage_.pop_front(); - } - } - } - return true; - });*/ + active_(true), + this_peer(ftl::protocol::id), + impl_(new ftl::net::NetImplDetail()), + peers_(10), + phase_(0), + periodic_time_(1.0), + reconnect_attempts_(5), + thread_(Universe::__start, this) { + _installBindings(); + + // Add an idle timer job to garbage collect peer objects + // Note: Important to be a timer job to ensure no other timer jobs are + // using the object. + // FIXME: Replace use of timer. + /*garbage_timer_ = ftl::timer::add(ftl::timer::kTimerIdle10, [this](int64_t ts) { + if (garbage_.size() > 0) { + UNIQUE_LOCK(net_mutex_,lk); + if (ftl::pool.n_idle() == ftl::pool.size()) { + if (garbage_.size() > 0) LOG(1) << "Garbage collection"; + while (garbage_.size() > 0) { + delete garbage_.front(); + garbage_.pop_front(); + } + } + } + return true; + });*/ } Universe::~Universe() { - shutdown(); - CHECK(peer_instances_ == 0); + shutdown(); + CHECK_EQ(peer_instances_, 0); } size_t Universe::getSendBufferSize(ftl::URI::scheme_t s) { - // TODO: Allow these to be configured again. - switch(s) { - case ftl::URI::scheme_t::SCHEME_WS: - case ftl::URI::scheme_t::SCHEME_WSS: - return WS_SEND_BUFFER_SIZE; + // TODO(Nick): Allow these to be configured again. + switch (s) { + case ftl::URI::scheme_t::SCHEME_WS: + case ftl::URI::scheme_t::SCHEME_WSS: + return WS_SEND_BUFFER_SIZE; - default: - return TCP_SEND_BUFFER_SIZE; - } + default: + return TCP_SEND_BUFFER_SIZE; + } } size_t Universe::getRecvBufferSize(ftl::URI::scheme_t s) { - switch(s) { - case ftl::URI::scheme_t::SCHEME_WS: - case ftl::URI::scheme_t::SCHEME_WSS: - return WS_RECEIVE_BUFFER_SIZE; - - default: - return TCP_RECEIVE_BUFFER_SIZE; - } + switch (s) { + case ftl::URI::scheme_t::SCHEME_WS: + case ftl::URI::scheme_t::SCHEME_WSS: + return WS_RECEIVE_BUFFER_SIZE; + default: + return TCP_RECEIVE_BUFFER_SIZE; + } } void Universe::start() { - - /*auto l = get<json_t>("listen"); - - if (l && (*l).is_array()) { - for (auto &ll : *l) { - listen(ftl::URI(ll)); - } - } else if (l && (*l).is_string()) { - listen(ftl::URI((*l).get<string>())); - } - - auto p = get<json_t>("peers"); - if (p && (*p).is_array()) { - for (auto &pp : *p) { - try { - connect(pp); - } catch (const ftl::exception &ex) { - LOG(ERROR) << "Could not connect to: " << std::string(pp); - } - } - }*/ + /*auto l = get<json_t>("listen"); + + if (l && (*l).is_array()) { + for (auto &ll : *l) { + listen(ftl::URI(ll)); + } + } else if (l && (*l).is_string()) { + listen(ftl::URI((*l).get<string>())); + } + + auto p = get<json_t>("peers"); + if (p && (*p).is_array()) { + for (auto &pp : *p) { + try { + connect(pp); + } catch (const ftl::exception &ex) { + LOG(ERROR) << "Could not connect to: " << std::string(pp); + } + } + }*/ } void Universe::shutdown() { - if (!active_) return; - DLOG(1) << "Cleanup Network ..."; + if (!active_) return; + DLOG(1) << "Cleanup Network ..."; - { - SHARED_LOCK(net_mutex_, lk); + { + SHARED_LOCK(net_mutex_, lk); - for (auto &l : listeners_) { - l->close(); - } + for (auto &l : listeners_) { + l->close(); + } - for (auto &s : peers_) { - if (s) s->rawClose(); - } - } + for (auto &s : peers_) { + if (s) s->rawClose(); + } + } - active_ = false; - thread_.join(); + active_ = false; + thread_.join(); - // FIXME: This shouldn't be needed - if (peer_instances_ > 0 && ftl::pool.size() > 0) { - DLOG(WARNING) << "Waiting on peer destruction... " << peer_instances_; - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - if (peer_instances_ > 0) LOG(FATAL) << "Peers not destroyed"; - } + // FIXME: This shouldn't be needed + if (peer_instances_ > 0 && ftl::pool.size() > 0) { + DLOG(WARNING) << "Waiting on peer destruction... " << peer_instances_; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + if (peer_instances_ > 0) LOG(FATAL) << "Peers not destroyed"; + } } bool Universe::listen(const ftl::URI &addr) { - try { - auto l = create_listener(addr); - l->bind(); - - { - UNIQUE_LOCK(net_mutex_,lk); - DLOG(1) << "listening on " << l->uri().to_string(); - listeners_.push_back(std::move(l)); - } - socket_cv_.notify_one(); - return true; - - } catch (const std::exception &ex) { - DLOG(INFO) << "Can't listen " << addr.to_string() << ", " << ex.what(); - _notifyError(nullptr, ftl::protocol::Error::kListen, ex.what()); - return false; - } + try { + auto l = create_listener(addr); + l->bind(); + + { + UNIQUE_LOCK(net_mutex_, lk); + DLOG(1) << "listening on " << l->uri().to_string(); + listeners_.push_back(std::move(l)); + } + socket_cv_.notify_one(); + return true; + } catch (const std::exception &ex) { + DLOG(INFO) << "Can't listen " << addr.to_string() << ", " << ex.what(); + _notifyError(nullptr, ftl::protocol::Error::kListen, ex.what()); + return false; + } } std::vector<ftl::URI> Universe::getListeningURIs() { - SHARED_LOCK(net_mutex_, lk); - std::vector<ftl::URI> uris(listeners_.size()); - std::transform(listeners_.begin(), listeners_.end(), uris.begin(), [](const auto &l){ return l->uri(); }); - return uris; + SHARED_LOCK(net_mutex_, lk); + std::vector<ftl::URI> uris(listeners_.size()); + std::transform(listeners_.begin(), listeners_.end(), uris.begin(), [](const auto &l){ return l->uri(); }); + return uris; } bool Universe::isConnected(const ftl::URI &uri) { - SHARED_LOCK(net_mutex_,lk); - return (peer_by_uri_.find(uri.getBaseURI()) != peer_by_uri_.end()); + SHARED_LOCK(net_mutex_, lk); + return (peer_by_uri_.find(uri.getBaseURI()) != peer_by_uri_.end()); } bool Universe::isConnected(const std::string &s) { - ftl::URI uri(s); - return isConnected(uri); + ftl::URI uri(s); + return isConnected(uri); } void Universe::_insertPeer(const PeerPtr &ptr) { - UNIQUE_LOCK(net_mutex_,lk); - for (size_t i=0; i<peers_.size(); ++i) { - if (!peers_[i]) { - ++connection_count_; - peers_[i] = ptr; - peer_by_uri_[ptr->getURIObject().getBaseURI()] = i; - peer_ids_[ptr->id()] = i; - ptr->local_id_ = i; - - lk.unlock(); - socket_cv_.notify_one(); - return; - } - } - throw FTL_Error("Too many connections"); + UNIQUE_LOCK(net_mutex_, lk); + for (size_t i = 0; i < peers_.size(); ++i) { + if (!peers_[i]) { + ++connection_count_; + peers_[i] = ptr; + peer_by_uri_[ptr->getURIObject().getBaseURI()] = i; + peer_ids_[ptr->id()] = i; + ptr->local_id_ = i; + + lk.unlock(); + socket_cv_.notify_one(); + return; + } + } + throw FTL_Error("Too many connections"); } PeerPtr Universe::connect(const ftl::URI &u) { + // Check if already connected or if self (when could this happen?) + { + SHARED_LOCK(net_mutex_, lk); + if (peer_by_uri_.find(u.getBaseURI()) != peer_by_uri_.end()) { + return peers_[peer_by_uri_.at(u.getBaseURI())]; + } + + if (u.getHost() == "localhost" || u.getHost() == "127.0.0.1") { + if (std::any_of( + listeners_.begin(), + listeners_.end(), + [u](const auto &l) { return l->port() == u.getPort(); })) { + throw FTL_Error("Cannot connect to self"); + } + } + } + + auto p = std::make_shared<Peer>(u, this, &disp_); + + _insertPeer(p); + _installBindings(p); + p->start(); - // Check if already connected or if self (when could this happen?) - { - SHARED_LOCK(net_mutex_,lk); - if (peer_by_uri_.find(u.getBaseURI()) != peer_by_uri_.end()) { - return peers_[peer_by_uri_.at(u.getBaseURI())]; - } - - if (u.getHost() == "localhost" || u.getHost() == "127.0.0.1") { - if (std::any_of(listeners_.begin(), listeners_.end(), [u](const auto &l) { return l->port() == u.getPort(); })) { - throw FTL_Error("Cannot connect to self"); - } - } - } - - auto p = std::make_shared<Peer>(u, this, &disp_); - - _insertPeer(p); - _installBindings(p); - p->start(); - - return p; + return p; } PeerPtr Universe::connect(const std::string& addr) { - return connect(ftl::URI(addr)); + return connect(ftl::URI(addr)); } void Universe::unbind(const std::string &name) { - UNIQUE_LOCK(net_mutex_,lk); - disp_.unbind(name); + UNIQUE_LOCK(net_mutex_, lk); + disp_.unbind(name); } int Universe::waitConnections(int seconds) { - SHARED_LOCK(net_mutex_, lk); - auto peers = peers_; - lk.unlock(); - return std::count_if(peers.begin(), peers.end(), [seconds](const auto &p) { - return p && p->waitConnection(seconds); - }); + SHARED_LOCK(net_mutex_, lk); + auto peers = peers_; + lk.unlock(); + return std::count_if(peers.begin(), peers.end(), [seconds](const auto &p) { + return p && p->waitConnection(seconds); + }); } void Universe::_setDescriptors() { - SHARED_LOCK(net_mutex_, lk); - - impl_->pollfds.clear(); - impl_->idMap.clear(); - - //Set file descriptor for the listening sockets. - for (auto &l : listeners_) { - if (l) { - auto sock = l->fd(); - if (sock != INVALID_SOCKET) { - pollfd fdentry; - #ifdef WIN32 - fdentry.events = POLLIN; - #else - fdentry.events = POLLIN; // | POLLERR; - #endif - fdentry.fd = sock; - fdentry.revents = 0; - impl_->pollfds.push_back(fdentry); - impl_->idMap[sock] = impl_->pollfds.size() - 1; - } - } - } - - //Set the file descriptors for each client - for (const auto &s : peers_) { - if (s && s->isValid()) { - auto sock = s->_socket(); - if (sock != INVALID_SOCKET) { - pollfd fdentry; - #ifdef WIN32 - fdentry.events = POLLIN; - #else - fdentry.events = POLLIN; // | POLLERR; - #endif - fdentry.fd = sock; - fdentry.revents = 0; - impl_->pollfds.push_back(fdentry); - impl_->idMap[sock] = impl_->pollfds.size() - 1; - } - } - } -} - -void Universe::_installBindings(const PeerPtr &p) { - -} - -void Universe::_installBindings() { - -} + SHARED_LOCK(net_mutex_, lk); + + impl_->pollfds.clear(); + impl_->idMap.clear(); + + // Set file descriptor for the listening sockets. + for (auto &l : listeners_) { + if (l) { + auto sock = l->fd(); + if (sock != INVALID_SOCKET) { + pollfd fdentry; + #ifdef WIN32 + fdentry.events = POLLIN; + #else + fdentry.events = POLLIN; // | POLLERR; + #endif + fdentry.fd = sock; + fdentry.revents = 0; + impl_->pollfds.push_back(fdentry); + impl_->idMap[sock] = impl_->pollfds.size() - 1; + } + } + } + + // Set the file descriptors for each client + for (const auto &s : peers_) { + if (s && s->isValid()) { + auto sock = s->_socket(); + if (sock != INVALID_SOCKET) { + pollfd fdentry; + #ifdef WIN32 + fdentry.events = POLLIN; + #else + fdentry.events = POLLIN; // | POLLERR; + #endif + fdentry.fd = sock; + fdentry.revents = 0; + impl_->pollfds.push_back(fdentry); + impl_->idMap[sock] = impl_->pollfds.size() - 1; + } + } + } +} + +void Universe::_installBindings(const PeerPtr &p) {} + +void Universe::_installBindings() {} void Universe::_removePeer(PeerPtr &p) { - UNIQUE_LOCK(net_mutex_, ulk); - - if (p && (!p->isValid() || - p->status() == NodeStatus::kReconnecting || - p->status() == NodeStatus::kDisconnected)) { + UNIQUE_LOCK(net_mutex_, ulk); - auto ix = peer_ids_.find(p->id()); - if (ix != peer_ids_.end()) peer_ids_.erase(ix); + if (p && (!p->isValid() || + p->status() == NodeStatus::kReconnecting || + p->status() == NodeStatus::kDisconnected)) { + auto ix = peer_ids_.find(p->id()); + if (ix != peer_ids_.end()) peer_ids_.erase(ix); - for (auto j=peer_by_uri_.begin(); j != peer_by_uri_.end(); ++j) { - if (peers_[j->second] == p) { - peer_by_uri_.erase(j); - break; - } - } + for (auto j=peer_by_uri_.begin(); j != peer_by_uri_.end(); ++j) { + if (peers_[j->second] == p) { + peer_by_uri_.erase(j); + break; + } + } - if (p->status() == NodeStatus::kReconnecting) { - reconnects_.push_back({reconnect_attempts_, 1.0f, p}); - } else { - garbage_.push_back(p); - } + if (p->status() == NodeStatus::kReconnecting) { + reconnects_.push_back({reconnect_attempts_, 1.0f, p}); + } else { + garbage_.push_back(p); + } - --connection_count_; + --connection_count_; - DLOG(1) << "Removing disconnected peer: " << p->id().to_string(); - on_disconnect_.triggerAsync(p); + DLOG(1) << "Removing disconnected peer: " << p->id().to_string(); + on_disconnect_.triggerAsync(p); - p.reset(); - } + p.reset(); + } } void Universe::_cleanupPeers() { - SHARED_LOCK(net_mutex_, lk); - auto i = peers_.begin(); - while (i != peers_.end()) { - auto &p = *i; - if (p && (!p->isValid() || - p->status() == NodeStatus::kReconnecting || - p->status() == NodeStatus::kDisconnected)) { - lk.unlock(); - _removePeer(p); - lk.lock(); - } - ++i; - } + SHARED_LOCK(net_mutex_, lk); + auto i = peers_.begin(); + while (i != peers_.end()) { + auto &p = *i; + if (p && (!p->isValid() || + p->status() == NodeStatus::kReconnecting || + p->status() == NodeStatus::kDisconnected)) { + lk.unlock(); + _removePeer(p); + lk.lock(); + } + ++i; + } } PeerPtr Universe::getPeer(const UUID &id) const { - SHARED_LOCK(net_mutex_,lk); - auto ix = peer_ids_.find(id); - if (ix == peer_ids_.end()) return nullptr; - else return peers_[ix->second]; + SHARED_LOCK(net_mutex_, lk); + auto ix = peer_ids_.find(id); + if (ix == peer_ids_.end()) return nullptr; + else + return peers_[ix->second]; } PeerPtr Universe::getWebService() const { - SHARED_LOCK(net_mutex_,lk); - auto it = std::find_if(peers_.begin(), peers_.end(), [](const auto &p) { - return p && p->getType() == NodeType::kWebService; - }); - return (it != peers_.end()) ? *it : nullptr; + SHARED_LOCK(net_mutex_, lk); + auto it = std::find_if(peers_.begin(), peers_.end(), [](const auto &p) { + return p && p->getType() == NodeType::kWebService; + }); + return (it != peers_.end()) ? *it : nullptr; } std::list<PeerPtr> Universe::getPeers() const { - SHARED_LOCK(net_mutex_,lk); - std::list<PeerPtr> result; - std::copy_if(peers_.begin(), peers_.end(), std::back_inserter(result), [](const PeerPtr &ptr){ return !!ptr; }); - return result; + SHARED_LOCK(net_mutex_, lk); + std::list<PeerPtr> result; + std::copy_if(peers_.begin(), peers_.end(), std::back_inserter(result), [](const PeerPtr &ptr){ return !!ptr; }); + return result; } void Universe::_periodic() { - auto i = reconnects_.begin(); - while (i != reconnects_.end()) { - - std::string addr = i->peer->getURI(); - - { - SHARED_LOCK(net_mutex_,lk); - ftl::URI u(addr); - bool removed = false; - - if (u.getHost() == "localhost" || u.getHost() == "127.0.0.1") { - for (const auto &l : listeners_) { - if (l->port() == u.getPort()) { - _notifyError(nullptr, ftl::protocol::Error::kSelfConnect, "Cannot connect to self"); - garbage_.push_back((*i).peer); - i = reconnects_.erase(i); - removed = true; - break; - } - } - } - - if (removed) continue; - } - - auto peer = i->peer; - _insertPeer(peer); - peer->status_ = NodeStatus::kConnecting; - i = reconnects_.erase(i); - //ftl::pool.push([peer](int id) { - peer->reconnect(); - //}); - - /*if ((*i).peer->reconnect()) { - _insertPeer((*i).peer); - i = reconnects_.erase(i); - } - else if ((*i).tries > 0) { - (*i).tries--; - i++; - } - else { - garbage_.push_back((*i).peer); - i = reconnects_.erase(i); - }*/ - } + auto i = reconnects_.begin(); + while (i != reconnects_.end()) { + std::string addr = i->peer->getURI(); + + { + SHARED_LOCK(net_mutex_, lk); + ftl::URI u(addr); + bool removed = false; + + if (u.getHost() == "localhost" || u.getHost() == "127.0.0.1") { + for (const auto &l : listeners_) { + if (l->port() == u.getPort()) { + _notifyError(nullptr, ftl::protocol::Error::kSelfConnect, "Cannot connect to self"); + garbage_.push_back((*i).peer); + i = reconnects_.erase(i); + removed = true; + break; + } + } + } + + if (removed) continue; + } + + auto peer = i->peer; + _insertPeer(peer); + peer->status_ = NodeStatus::kConnecting; + i = reconnects_.erase(i); + // ftl::pool.push([peer](int id) { + peer->reconnect(); + // }); + + /*if ((*i).peer->reconnect()) { + _insertPeer((*i).peer); + i = reconnects_.erase(i); + } + else if ((*i).tries > 0) { + (*i).tries--; + i++; + } + else { + garbage_.push_back((*i).peer); + i = reconnects_.erase(i); + }*/ + } } void Universe::__start(Universe *u) { #ifndef WIN32 - // TODO: move somewhere else (common initialization file?) - signal(SIGPIPE,SIG_IGN); + // TODO(Seb): move somewhere else (common initialization file?) + signal(SIGPIPE, SIG_IGN); #endif // WIN32 - u->_run(); + u->_run(); } void Universe::_run() { - auto start = std::chrono::high_resolution_clock::now(); - - while (active_) { - _setDescriptors(); - int selres = 1; - - _cleanupPeers(); - - // Do periodics - auto now = std::chrono::high_resolution_clock::now(); - std::chrono::duration<double> elapsed = now - start; - if (elapsed.count() >= periodic_time_) { - start = now; - _periodic(); - } - - // It is an error to use "select" with no sockets ... so just sleep - if (impl_->pollfds.size() == 0) { - std::shared_lock lk(net_mutex_); - socket_cv_.wait_for(lk, std::chrono::milliseconds(100), [this](){ return listeners_.size() > 0 || connection_count_ > 0; }); - continue; - } - - #ifdef WIN32 - selres = WSAPoll(impl_->pollfds.data(), impl_->pollfds.size(), 100); - #else - selres = poll(impl_->pollfds.data(), impl_->pollfds.size(), 100); - #endif - - //Some kind of error occured, it is usually possible to recover from this. - if (selres < 0) { - #ifdef WIN32 - int errNum = WSAGetLastError(); - switch (errNum) { - case WSAENOTSOCK : continue; // Socket was closed - default : DLOG(WARNING) << "Unhandled poll error: " << errNum; - } - #else - switch (errno) { - case 9 : continue; // Bad file descriptor = socket closed - case 4 : continue; // Interrupted system call ... no problem - default : DLOG(WARNING) << "Unhandled poll error: " << strerror(errno) << "(" << errno << ")"; - } - #endif - continue; - } else if (selres == 0) { - // Timeout, nothing to do... - continue; - } - - SHARED_LOCK(net_mutex_,lk); - - //If connection request is waiting - for (auto &l : listeners_) { - if (l && l->is_listening() && (impl_->pollfds[impl_->idMap[l->fd()]].revents & POLLIN)) { - std::unique_ptr<ftl::net::internal::SocketConnection> csock; - try { - csock = l->accept(); - } catch (const std::exception &ex) { - _notifyError(nullptr, ftl::protocol::Error::kConnectionFailed, ex.what()); - } - - lk.unlock(); - - if (csock) { - auto p = std::make_shared<Peer>(std::move(csock), this, &disp_); - _insertPeer(p); - p->start(); - } - - lk.lock(); - } - } - - - // Also check each clients socket to see if any messages or errors are waiting - for (size_t p=0; p<peers_.size(); ++p) { - auto s = peers_[(p+phase_)%peers_.size()]; - - if (s && s->isValid()) { - // Note: It is possible that the socket becomes invalid after check but before - // looking at the FD sets, therefore cache the original socket - SOCKET sock = s->_socket(); - if (sock == INVALID_SOCKET) continue; - - if (impl_->idMap.count(sock) == 0) continue; - - const auto &fdstruct = impl_->pollfds[impl_->idMap[sock]]; - - // This is needed on Windows to detect socket close. - if (fdstruct.revents & POLLERR) { - if (s->socketError()) { - //lk.unlock(); - //s->close(); - //lk.lock(); - continue; // No point in reading data... - } - } - //If message received from this client then deal with it - if (fdstruct.revents & POLLIN) { - lk.unlock(); - s->data(); - lk.lock(); - } - } - } - ++phase_; - } - - // Garbage is a threadsafe container, moving there first allows the destructor to be called - // without the lock. - { - UNIQUE_LOCK(net_mutex_,lk); - garbage_.insert(garbage_.end(), peers_.begin(), peers_.end()); - reconnects_.clear(); - peers_.clear(); - peer_by_uri_.clear(); - peer_ids_.clear(); - listeners_.clear(); - } - - garbage_.clear(); + auto start = std::chrono::high_resolution_clock::now(); + + while (active_) { + _setDescriptors(); + int selres = 1; + + _cleanupPeers(); + + // Do periodics + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = now - start; + if (elapsed.count() >= periodic_time_) { + start = now; + _periodic(); + } + + // It is an error to use "select" with no sockets ... so just sleep + if (impl_->pollfds.size() == 0) { + std::shared_lock lk(net_mutex_); + socket_cv_.wait_for( + lk, + milliseconds(100), + [this](){ return listeners_.size() > 0 || connection_count_ > 0; }); + continue; + } + + #ifdef WIN32 + selres = WSAPoll(impl_->pollfds.data(), impl_->pollfds.size(), 100); + #else + selres = poll(impl_->pollfds.data(), impl_->pollfds.size(), 100); + #endif + + // Some kind of error occured, it is usually possible to recover from this. + if (selres < 0) { + #ifdef WIN32 + int errNum = WSAGetLastError(); + switch (errNum) { + case WSAENOTSOCK : continue; // Socket was closed + default : DLOG(WARNING) << "Unhandled poll error: " << errNum; + } + #else + switch (errno) { + case 9 : continue; // Bad file descriptor = socket closed + case 4 : continue; // Interrupted system call ... no problem + default : DLOG(WARNING) << "Unhandled poll error: " << strerror(errno) << "(" << errno << ")"; + } + #endif + continue; + } else if (selres == 0) { + // Timeout, nothing to do... + continue; + } + + SHARED_LOCK(net_mutex_, lk); + + // If connection request is waiting + for (auto &l : listeners_) { + if (l && l->is_listening() && (impl_->pollfds[impl_->idMap[l->fd()]].revents & POLLIN)) { + std::unique_ptr<ftl::net::internal::SocketConnection> csock; + try { + csock = l->accept(); + } catch (const std::exception &ex) { + _notifyError(nullptr, ftl::protocol::Error::kConnectionFailed, ex.what()); + } + + lk.unlock(); + + if (csock) { + auto p = std::make_shared<Peer>(std::move(csock), this, &disp_); + _insertPeer(p); + p->start(); + } + + lk.lock(); + } + } + + + // Also check each clients socket to see if any messages or errors are waiting + for (size_t p = 0; p < peers_.size(); ++p) { + auto s = peers_[(p+phase_)%peers_.size()]; + + if (s && s->isValid()) { + // Note: It is possible that the socket becomes invalid after check but before + // looking at the FD sets, therefore cache the original socket + SOCKET sock = s->_socket(); + if (sock == INVALID_SOCKET) continue; + + if (impl_->idMap.count(sock) == 0) continue; + + const auto &fdstruct = impl_->pollfds[impl_->idMap[sock]]; + + // This is needed on Windows to detect socket close. + if (fdstruct.revents & POLLERR) { + if (s->socketError()) { + continue; // No point in reading data... + } + } + // If message received from this client then deal with it + if (fdstruct.revents & POLLIN) { + lk.unlock(); + s->data(); + lk.lock(); + } + } + } + ++phase_; + } + + // Garbage is a threadsafe container, moving there first allows the destructor to be called + // without the lock. + { + UNIQUE_LOCK(net_mutex_, lk); + garbage_.insert(garbage_.end(), peers_.begin(), peers_.end()); + reconnects_.clear(); + peers_.clear(); + peer_by_uri_.clear(); + peer_ids_.clear(); + listeners_.clear(); + } + + garbage_.clear(); } ftl::Handle Universe::onConnect(const std::function<bool(const PeerPtr&)> &cb) { - return on_connect_.on(cb); + return on_connect_.on(cb); } ftl::Handle Universe::onDisconnect(const std::function<bool(const PeerPtr&)> &cb) { - return on_disconnect_.on(cb); + return on_disconnect_.on(cb); } -ftl::Handle Universe::onError(const std::function<bool(const PeerPtr&, ftl::protocol::Error, const std::string &)> &cb) { - return on_error_.on(cb); +ftl::Handle Universe::onError( + const std::function<bool(const PeerPtr&, ftl::protocol::Error, const std::string &)> &cb) { + return on_error_.on(cb); } PeerPtr Universe::injectFakePeer(std::unique_ptr<ftl::net::internal::SocketConnection> s) { - auto p = std::make_shared<Peer>(std::move(s), this, &disp_); - _insertPeer(p); - _installBindings(p); - return p; + auto p = std::make_shared<Peer>(std::move(s), this, &disp_); + _insertPeer(p); + _installBindings(p); + return p; } PeerPtr Universe::_findPeer(const Peer *p) { - SHARED_LOCK(net_mutex_,lk); - for (const auto &pp : peers_) { - if (pp.get() == p) return pp; - } - return nullptr; + SHARED_LOCK(net_mutex_, lk); + for (const auto &pp : peers_) { + if (pp.get() == p) return pp; + } + return nullptr; } void Universe::_notifyConnect(Peer *p) { - const auto ptr = _findPeer(p); + const auto ptr = _findPeer(p); - // The peer could have been removed from valid peers already. - if (!ptr) return; + // The peer could have been removed from valid peers already. + if (!ptr) return; - { - UNIQUE_LOCK(net_mutex_,lk); - peer_ids_[ptr->id()] = ptr->local_id_; - } + { + UNIQUE_LOCK(net_mutex_, lk); + peer_ids_[ptr->id()] = ptr->local_id_; + } - on_connect_.triggerAsync(ptr); + on_connect_.triggerAsync(ptr); } void Universe::_notifyDisconnect(Peer *p) { - const auto ptr = _findPeer(p); - if (!ptr) return; + const auto ptr = _findPeer(p); + if (!ptr) return; - on_disconnect_.triggerAsync(ptr); + on_disconnect_.triggerAsync(ptr); } void Universe::_notifyError(Peer *p, ftl::protocol::Error e, const std::string &errstr) { - LOG(ERROR) << "Net Error (" << int(e) << "): " << errstr; - const auto ptr = (p) ? _findPeer(p) : nullptr; - - // Note: Net errors can have no peer - //if (!ptr) return; + LOG(ERROR) << "Net Error (" << int(e) << "): " << errstr; + const auto ptr = (p) ? _findPeer(p) : nullptr; - on_error_.triggerAsync(ptr, e, errstr); + on_error_.triggerAsync(ptr, e, errstr); } diff --git a/src/universe.hpp b/src/universe.hpp index 439c8d724a33336e0638015a10a9ae20fba2d9e2..942557b355345af9c2ba43263be4e253d8e025ba 100644 --- a/src/universe.hpp +++ b/src/universe.hpp @@ -6,6 +6,14 @@ #pragma once +#include <vector> +#include <list> +#include <string> +#include <thread> +#include <map> +#include <unordered_map> +#include <memory> + #include <msgpack.hpp> #include <ftl/protocol.hpp> @@ -19,23 +27,13 @@ #include <nlohmann/json_fwd.hpp> -#include <vector> -#include <list> -#include <string> -#include <thread> -#include <map> - namespace ftl { namespace net { -struct Error { - int errno; -}; - struct ReconnectInfo { - int tries; - float delay; - PeerPtr peer; + int tries; + float delay; + PeerPtr peer; }; struct NetImplDetail; @@ -52,314 +50,316 @@ using Callback = unsigned int; * their actions. */ class Universe { -public: - friend class Peer; - - Universe(); - - /** - * The destructor will terminate the network thread before completing. - */ - ~Universe(); - - void start(); - - /** - * Open a new listening port on a given interfaces. - * eg. "tcp://localhost:9000" - * @param addr URI giving protocol, interface and port - */ - bool listen(const ftl::URI &addr); - - std::vector<ftl::URI> getListeningURIs(); - - /** - * Essential to call this before destroying anything that registered - * callbacks or binds for RPC. It will terminate all connections and - * stop any network activity but without deleting the net object. - */ - void shutdown(); - - /** - * Create a new peer connection. - * eg. "tcp://10.0.0.2:9000" - * Supported protocols include tcp and ws. - * - * @param addr URI giving protocol, interface and port - */ - PeerPtr connect(const std::string &addr); - PeerPtr connect(const ftl::URI &addr); - - bool isConnected(const ftl::URI &uri); - bool isConnected(const std::string &s); - - size_t numberOfPeers() const { return connection_count_; } - - /** - * Will block until all currently registered connnections have completed. - * You should not use this, but rather use onConnect. - */ - int waitConnections(int seconds = 1); - - /** get peer pointer by peer UUID, returns nullptr if not found */ - PeerPtr getPeer(const ftl::UUID &pid) const; - /** get webservice peer pointer, returns nullptr if not connected to webservice */ - PeerPtr getWebService() const; - - std::list<PeerPtr> getPeers() const; - - /** - * Bind a function to an RPC or service call name. This will implicitely - * be called by any peer making the request. - */ - template <typename F> - void bind(const std::string &name, F func); - - void unbind(const std::string &name); - - /** - * Check if an RPC name is already bound. - */ - inline bool isBound(const std::string &name) const { return disp_.isBound(name); } - - /** - * Send a non-blocking RPC call with no return value to all connected - * peers. - */ - template <typename... ARGS> - void broadcast(const std::string &name, ARGS... args); - - template <typename R, typename... ARGS> - R call(const UUID &pid, const std::string &name, ARGS... args); - - /** - * Non-blocking Remote Procedure Call using a callback function. - * - * @param pid Peer GUID - * @param name RPC Function name. - * @param cb Completion callback. - * @param args A variable number of arguments for RPC function. - * - * @return A call id for use with cancelCall() if needed. - */ - template <typename R, typename... ARGS> - int asyncCall(const UUID &pid, const std::string &name, - std::function<void(const R&)> cb, - ARGS... args); - - template <typename... ARGS> - bool send(const UUID &pid, const std::string &name, ARGS... args); - - template <typename... ARGS> - int try_send(const UUID &pid, const std::string &name, ARGS... args); - - template <typename R, typename... ARGS> - std::optional<R> findOne(const std::string &name, ARGS... args); - - template <typename R, typename... ARGS> - std::vector<R> findAll(const std::string &name, ARGS... args); - - void setLocalID(const ftl::UUID &u) { this_peer = u; }; - const ftl::UUID &id() const { return this_peer; } - - // --- Event Handlers ----------------------------------------------------- - - ftl::Handle onConnect(const std::function<bool(const ftl::net::PeerPtr&)>&); - ftl::Handle onDisconnect(const std::function<bool(const ftl::net::PeerPtr&)>&); - ftl::Handle onError(const std::function<bool(const ftl::net::PeerPtr&, ftl::protocol::Error, const std::string &)>&); - - size_t getSendBufferSize(ftl::URI::scheme_t s); - size_t getRecvBufferSize(ftl::URI::scheme_t s); - - static inline std::shared_ptr<Universe> getInstance() { return instance_; } - - // --- Test support ------------------------------------------------------- - - PeerPtr injectFakePeer(std::unique_ptr<ftl::net::internal::SocketConnection> s); - -private: - void _run(); - void _setDescriptors(); - void _installBindings(); - void _installBindings(const ftl::net::PeerPtr&); - void _cleanupPeers(); - void _notifyConnect(ftl::net::Peer *); - void _notifyDisconnect(ftl::net::Peer *); - void _notifyError(ftl::net::Peer *, ftl::protocol::Error, const std::string &); - void _periodic(); - ftl::net::PeerPtr _findPeer(const ftl::net::Peer *p); - void _removePeer(PeerPtr &p); - void _insertPeer(const ftl::net::PeerPtr &ptr); - - static void __start(Universe *u); - - bool active_; - ftl::UUID this_peer; - mutable SHARED_MUTEX net_mutex_; - std::condition_variable_any socket_cv_; - - std::unique_ptr<NetImplDetail> impl_; - - std::vector<std::unique_ptr<ftl::net::internal::SocketServer>> listeners_; - std::vector<ftl::net::PeerPtr> peers_; - std::unordered_map<std::string, size_t> peer_by_uri_; - std::map<ftl::UUID, size_t> peer_ids_; - - ftl::net::Dispatcher disp_; - std::list<ReconnectInfo> reconnects_; - size_t phase_; - std::list<ftl::net::PeerPtr> garbage_; - ftl::Handle garbage_timer_; - - // size_t send_size_; - // size_t recv_size_; - double periodic_time_; - int reconnect_attempts_; - std::atomic_int connection_count_ = 0; // Active connections - std::atomic_int peer_instances_ = 0; // Actual peers dependent on Universe - - ftl::Handler<const ftl::net::PeerPtr&> on_connect_; - ftl::Handler<const ftl::net::PeerPtr&> on_disconnect_; - ftl::Handler<const ftl::net::PeerPtr&, ftl::protocol::Error, const std::string &> on_error_; - - static std::shared_ptr<Universe> instance_; - - // NOTE: Must always be last member - std::thread thread_; + public: + friend class Peer; + + Universe(); + + /** + * The destructor will terminate the network thread before completing. + */ + ~Universe(); + + void start(); + + /** + * Open a new listening port on a given interfaces. + * eg. "tcp://localhost:9000" + * @param addr URI giving protocol, interface and port + */ + bool listen(const ftl::URI &addr); + + std::vector<ftl::URI> getListeningURIs(); + + /** + * Essential to call this before destroying anything that registered + * callbacks or binds for RPC. It will terminate all connections and + * stop any network activity but without deleting the net object. + */ + void shutdown(); + + /** + * Create a new peer connection. + * eg. "tcp://10.0.0.2:9000" + * Supported protocols include tcp and ws. + * + * @param addr URI giving protocol, interface and port + */ + PeerPtr connect(const std::string &addr); + PeerPtr connect(const ftl::URI &addr); + + bool isConnected(const ftl::URI &uri); + bool isConnected(const std::string &s); + + size_t numberOfPeers() const { return connection_count_; } + + /** + * Will block until all currently registered connnections have completed. + * You should not use this, but rather use onConnect. + */ + int waitConnections(int seconds = 1); + + /** get peer pointer by peer UUID, returns nullptr if not found */ + PeerPtr getPeer(const ftl::UUID &pid) const; + /** get webservice peer pointer, returns nullptr if not connected to webservice */ + PeerPtr getWebService() const; + + std::list<PeerPtr> getPeers() const; + + /** + * Bind a function to an RPC or service call name. This will implicitely + * be called by any peer making the request. + */ + template <typename F> + void bind(const std::string &name, F func); + + void unbind(const std::string &name); + + /** + * Check if an RPC name is already bound. + */ + inline bool isBound(const std::string &name) const { return disp_.isBound(name); } + + /** + * Send a non-blocking RPC call with no return value to all connected + * peers. + */ + template <typename... ARGS> + void broadcast(const std::string &name, ARGS... args); + + template <typename R, typename... ARGS> + R call(const UUID &pid, const std::string &name, ARGS... args); + + /** + * Non-blocking Remote Procedure Call using a callback function. + * + * @param pid Peer GUID + * @param name RPC Function name. + * @param cb Completion callback. + * @param args A variable number of arguments for RPC function. + * + * @return A call id for use with cancelCall() if needed. + */ + template <typename R, typename... ARGS> + int asyncCall(const UUID &pid, const std::string &name, + std::function<void(const R&)> cb, + ARGS... args); + + template <typename... ARGS> + bool send(const UUID &pid, const std::string &name, ARGS... args); + + template <typename... ARGS> + int try_send(const UUID &pid, const std::string &name, ARGS... args); + + template <typename R, typename... ARGS> + std::optional<R> findOne(const std::string &name, ARGS... args); + + template <typename R, typename... ARGS> + std::vector<R> findAll(const std::string &name, ARGS... args); + + void setLocalID(const ftl::UUID &u) { this_peer = u; } + const ftl::UUID &id() const { return this_peer; } + + // --- Event Handlers ----------------------------------------------------- + + ftl::Handle onConnect(const std::function<bool(const ftl::net::PeerPtr&)>&); + ftl::Handle onDisconnect(const std::function<bool(const ftl::net::PeerPtr&)>&); + ftl::Handle onError( + const std::function<bool(const ftl::net::PeerPtr&, ftl::protocol::Error, const std::string &)>&); + + size_t getSendBufferSize(ftl::URI::scheme_t s); + size_t getRecvBufferSize(ftl::URI::scheme_t s); + + static inline std::shared_ptr<Universe> getInstance() { return instance_; } + + // --- Test support ------------------------------------------------------- + + PeerPtr injectFakePeer(std::unique_ptr<ftl::net::internal::SocketConnection> s); + + private: + void _run(); + void _setDescriptors(); + void _installBindings(); + void _installBindings(const ftl::net::PeerPtr&); + void _cleanupPeers(); + void _notifyConnect(ftl::net::Peer *); + void _notifyDisconnect(ftl::net::Peer *); + void _notifyError(ftl::net::Peer *, ftl::protocol::Error, const std::string &); + void _periodic(); + ftl::net::PeerPtr _findPeer(const ftl::net::Peer *p); + void _removePeer(PeerPtr &p); + void _insertPeer(const ftl::net::PeerPtr &ptr); + + static void __start(Universe *u); + + bool active_; + ftl::UUID this_peer; + mutable SHARED_MUTEX net_mutex_; + std::condition_variable_any socket_cv_; + + std::unique_ptr<NetImplDetail> impl_; + + std::vector<std::unique_ptr<ftl::net::internal::SocketServer>> listeners_; + std::vector<ftl::net::PeerPtr> peers_; + std::unordered_map<std::string, size_t> peer_by_uri_; + std::map<ftl::UUID, size_t> peer_ids_; + + ftl::net::Dispatcher disp_; + std::list<ReconnectInfo> reconnects_; + size_t phase_; + std::list<ftl::net::PeerPtr> garbage_; + ftl::Handle garbage_timer_; + + // size_t send_size_; + // size_t recv_size_; + double periodic_time_; + int reconnect_attempts_; + std::atomic_int connection_count_ = 0; // Active connections + std::atomic_int peer_instances_ = 0; // Actual peers dependent on Universe + + ftl::Handler<const ftl::net::PeerPtr&> on_connect_; + ftl::Handler<const ftl::net::PeerPtr&> on_disconnect_; + ftl::Handler<const ftl::net::PeerPtr&, ftl::protocol::Error, const std::string &> on_error_; + + static std::shared_ptr<Universe> instance_; + + // NOTE: Must always be last member + std::thread thread_; }; //------------------------------------------------------------------------------ template <typename F> void Universe::bind(const std::string &name, F func) { - UNIQUE_LOCK(net_mutex_,lk); - disp_.bind(name, func, - typename ftl::internal::func_kind_info<F>::result_kind(), - typename ftl::internal::func_kind_info<F>::args_kind(), - typename ftl::internal::func_kind_info<F>::has_peer()); + UNIQUE_LOCK(net_mutex_, lk); + disp_.bind(name, func, + typename ftl::internal::func_kind_info<F>::result_kind(), + typename ftl::internal::func_kind_info<F>::args_kind(), + typename ftl::internal::func_kind_info<F>::has_peer()); } template <typename... ARGS> void Universe::broadcast(const std::string &name, ARGS... args) { - SHARED_LOCK(net_mutex_,lk); - for (const auto &p : peers_) { - if (!p || !p->waitConnection()) continue; - p->send(name, args...); - } + SHARED_LOCK(net_mutex_, lk); + for (const auto &p : peers_) { + if (!p || !p->waitConnection()) continue; + p->send(name, args...); + } } template <typename R, typename... ARGS> std::optional<R> Universe::findOne(const std::string &name, ARGS... args) { - struct SharedData { - std::atomic_bool hasreturned = false; - std::mutex m; - std::condition_variable cv; - std::optional<R> result; - }; - - auto sdata = std::make_shared<SharedData>(); - - auto handler = [sdata](const std::optional<R> &r) { - std::unique_lock<std::mutex> lk(sdata->m); - if (r && !sdata->hasreturned) { - sdata->hasreturned = true; - sdata->result = r; - } - lk.unlock(); - sdata->cv.notify_one(); - }; - - { - SHARED_LOCK(net_mutex_,lk); - for (const auto &p : peers_) { - if (!p || !p->waitConnection()) continue; - p->asyncCall<std::optional<R>>(name, handler, args...); - } - } - - // Block thread until async callback notifies us - std::unique_lock<std::mutex> llk(sdata->m); - sdata->cv.wait_for(llk, std::chrono::seconds(1), [sdata] { - return (bool)sdata->hasreturned; - }); - - return sdata->result; + struct SharedData { + std::atomic_bool hasreturned = false; + std::mutex m; + std::condition_variable cv; + std::optional<R> result; + }; + + auto sdata = std::make_shared<SharedData>(); + + auto handler = [sdata](const std::optional<R> &r) { + std::unique_lock<std::mutex> lk(sdata->m); + if (r && !sdata->hasreturned) { + sdata->hasreturned = true; + sdata->result = r; + } + lk.unlock(); + sdata->cv.notify_one(); + }; + + { + SHARED_LOCK(net_mutex_, lk); + for (const auto &p : peers_) { + if (!p || !p->waitConnection()) continue; + p->asyncCall<std::optional<R>>(name, handler, args...); + } + } + + // Block thread until async callback notifies us + std::unique_lock<std::mutex> llk(sdata->m); + sdata->cv.wait_for(llk, std::chrono::seconds(1), [sdata] { + return static_cast<bool>(sdata->hasreturned); + }); + + return sdata->result; } template <typename R, typename... ARGS> std::vector<R> Universe::findAll(const std::string &name, ARGS... args) { - struct SharedData { - std::atomic_int returncount = 0; - std::atomic_int sentcount = 0; - std::mutex m; - std::condition_variable cv; - std::vector<R> results; - }; - - auto sdata = std::make_shared<SharedData>(); - - auto handler = [sdata](const std::vector<R> &r) { - std::unique_lock<std::mutex> lk(sdata->m); - ++sdata->returncount; - sdata->results.insert(sdata->results.end(), r.begin(), r.end()); - lk.unlock(); - sdata->cv.notify_one(); - }; - - { - SHARED_LOCK(net_mutex_,lk); - for (const auto &p : peers_) { - if (!p || !p->waitConnection()) continue; - ++sdata->sentcount; - p->asyncCall<std::vector<R>>(name, handler, args...); - } - } - - std::unique_lock<std::mutex> llk(sdata->m); - sdata->cv.wait_for(llk, std::chrono::seconds(1), [sdata]{return sdata->returncount == sdata->sentcount; }); - return sdata->results; + struct SharedData { + std::atomic_int returncount = 0; + std::atomic_int sentcount = 0; + std::mutex m; + std::condition_variable cv; + std::vector<R> results; + }; + + auto sdata = std::make_shared<SharedData>(); + + auto handler = [sdata](const std::vector<R> &r) { + std::unique_lock<std::mutex> lk(sdata->m); + ++sdata->returncount; + sdata->results.insert(sdata->results.end(), r.begin(), r.end()); + lk.unlock(); + sdata->cv.notify_one(); + }; + + { + SHARED_LOCK(net_mutex_, lk); + for (const auto &p : peers_) { + if (!p || !p->waitConnection()) continue; + ++sdata->sentcount; + p->asyncCall<std::vector<R>>(name, handler, args...); + } + } + + std::unique_lock<std::mutex> llk(sdata->m); + sdata->cv.wait_for(llk, std::chrono::seconds(1), [sdata]{return sdata->returncount == sdata->sentcount; }); + return sdata->results; } template <typename R, typename... ARGS> R Universe::call(const ftl::UUID &pid, const std::string &name, ARGS... args) { - PeerPtr p = getPeer(pid); - if (p == nullptr || !p->isConnected()) { - if (p == nullptr) throw FTL_Error("Attempting to call an unknown peer : " << pid.to_string()); - else throw FTL_Error("Attempting to call an disconnected peer : " << pid.to_string()); - } - return p->call<R>(name, args...); + PeerPtr p = getPeer(pid); + if (p == nullptr || !p->isConnected()) { + if (p == nullptr) throw FTL_Error("Attempting to call an unknown peer : " << pid.to_string()); + else + throw FTL_Error("Attempting to call an disconnected peer : " << pid.to_string()); + } + return p->call<R>(name, args...); } template <typename R, typename... ARGS> int Universe::asyncCall(const ftl::UUID &pid, const std::string &name, std::function<void(const R&)> cb, ARGS... args) { - PeerPtr p = getPeer(pid); - if (p == nullptr || !p->isConnected()) { - if (p == nullptr) throw FTL_Error("Attempting to call an unknown peer : " << pid.to_string()); - else throw FTL_Error("Attempting to call an disconnected peer : " << pid.to_string()); - } - return p->asyncCall(name, cb, args...); + PeerPtr p = getPeer(pid); + if (p == nullptr || !p->isConnected()) { + if (p == nullptr) throw FTL_Error("Attempting to call an unknown peer : " << pid.to_string()); + else + throw FTL_Error("Attempting to call an disconnected peer : " << pid.to_string()); + } + return p->asyncCall(name, cb, args...); } template <typename... ARGS> bool Universe::send(const ftl::UUID &pid, const std::string &name, ARGS... args) { - PeerPtr p = getPeer(pid); - if (p == nullptr) { - LOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string(); - return false; - } + PeerPtr p = getPeer(pid); + if (p == nullptr) { + LOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string(); + return false; + } - return p->isConnected() && p->send(name, args...) > 0; + return p->isConnected() && p->send(name, args...) > 0; } template <typename... ARGS> int Universe::try_send(const ftl::UUID &pid, const std::string &name, ARGS... args) { - PeerPtr p = getPeer(pid); - if (p == nullptr) { - //DLOG(WARNING) << "Attempting to call an unknown peer : " << pid.to_string(); - return false; - } + PeerPtr p = getPeer(pid); + if (p == nullptr) { + return false; + } - return (p->isConnected()) ? p->try_send(name, args...) : -1; + return (p->isConnected()) ? p->try_send(name, args...) : -1; } }; // namespace net diff --git a/src/uri.cpp b/src/uri.cpp index 7432f82c7d245c4ab01dab8fb05bdce2dffbc86c..fcb90dfb1aa2217a79cff38773821ad6618651c1 100644 --- a/src/uri.cpp +++ b/src/uri.cpp @@ -4,10 +4,11 @@ * @author Nicolas Pope */ +#include <cstdlib> #include <ftl/uri.hpp> #include <nlohmann/json.hpp> // #include <filesystem> TODO When available -#include <cstdlib> + #include <ftl/lib/loguru.hpp> #ifndef WIN32 @@ -21,238 +22,238 @@ using ftl::uri_t; using std::string; URI::URI(uri_t puri) { - _parse(puri); + _parse(puri); } URI::URI(const std::string &puri) { - _parse(puri.c_str()); + _parse(puri.c_str()); } URI::URI(const URI &c) { - m_valid = c.m_valid; - m_host = c.m_host; - m_port = c.m_port; - m_proto = c.m_proto; - m_path = c.m_path; - m_pathseg = c.m_pathseg; - m_qmap = c.m_qmap; - m_base = c.m_base; - m_userinfo = c.m_userinfo; - m_frag = c.m_frag; + m_valid = c.m_valid; + m_host = c.m_host; + m_port = c.m_port; + m_proto = c.m_proto; + m_path = c.m_path; + m_pathseg = c.m_pathseg; + m_qmap = c.m_qmap; + m_base = c.m_base; + m_userinfo = c.m_userinfo; + m_frag = c.m_frag; } void URI::_parse(uri_t puri) { - UriUriA uri; - - std::string suri = puri; - - // NOTE: Non-standard additions to allow for Unix style relative file names. - if (suri[0] == '.') { - char cwdbuf[1024]; - if (getcwd(cwdbuf, 1024)) { - suri = string("file://") + string(cwdbuf) + suri.substr(1); - } - } else if (suri[0] == '/') { - suri = std::string("file://") + suri; - } else if (suri[0] == '~') { + UriUriA uri; + + std::string suri = puri; + + // NOTE: Non-standard additions to allow for Unix style relative file names. + if (suri[0] == '.') { + char cwdbuf[1024]; + if (getcwd(cwdbuf, 1024)) { + suri = string("file://") + string(cwdbuf) + suri.substr(1); + } + } else if (suri[0] == '/') { + suri = std::string("file://") + suri; + } else if (suri[0] == '~') { #ifdef WIN32 - suri = string("file://") + string(std::getenv("HOMEDRIVE")) + string(std::getenv("HOMEPATH")) + suri.substr(1); + suri = string("file://") + string(std::getenv("HOMEDRIVE")) + string(std::getenv("HOMEPATH")) + suri.substr(1); #else - suri = string("file://") + string(std::getenv("HOME")) + suri.substr(1); + suri = string("file://") + string(std::getenv("HOME")) + suri.substr(1); #endif - } + } #ifdef HAVE_URIPARSESINGLE - const char *errpos; - if (uriParseSingleUriA(&uri, puri, &errpos) != URI_SUCCESS) { + const char *errpos; + if (uriParseSingleUriA(&uri, puri, &errpos) != URI_SUCCESS) { #else - UriParserStateA uris; - uris.uri = &uri; - if (uriParseUriA(&uris, suri.c_str()) != URI_SUCCESS) { + UriParserStateA uris; + uris.uri = &uri; + if (uriParseUriA(&uris, suri.c_str()) != URI_SUCCESS) { #endif - m_valid = false; - m_host = "none"; - m_port = -1; - m_proto = SCHEME_NONE; - m_base = suri; - m_path = ""; - m_frag = ""; - } else { - m_host = std::string(uri.hostText.first, uri.hostText.afterLast - uri.hostText.first); - - std::string prototext = std::string(uri.scheme.first, uri.scheme.afterLast - uri.scheme.first); - if (prototext == "tcp") m_proto = SCHEME_TCP; - else if (prototext == "udp") m_proto = SCHEME_UDP; - else if (prototext == "ftl") m_proto = SCHEME_FTL; - else if (prototext == "http") m_proto = SCHEME_HTTP; - else if (prototext == "ws") m_proto = SCHEME_WS; - else if (prototext == "wss") m_proto = SCHEME_WSS; - else if (prototext == "ipc") m_proto = SCHEME_IPC; - else if (prototext == "device") m_proto = SCHEME_DEVICE; - else if (prototext == "file") m_proto = SCHEME_FILE; - else if (prototext == "group") m_proto = SCHEME_GROUP; - else m_proto = SCHEME_OTHER; - m_protostr = prototext; - - std::string porttext = std::string(uri.portText.first, uri.portText.afterLast - uri.portText.first); - m_port = atoi(porttext.c_str()); - m_userinfo = std::string(uri.userInfo.first, uri.userInfo.afterLast - uri.userInfo.first); - - for (auto h=uri.pathHead; h!=NULL; h=h->next) { - auto pstr = std::string( - h->text.first, h->text.afterLast - h->text.first); - - m_path += "/"; - m_path += pstr; - m_pathseg.push_back(pstr); - } - - //string query = std::string(uri.query.first, uri.query.afterLast - uri.query.first); - if (uri.query.afterLast - uri.query.first > 0) { - UriQueryListA *queryList; - int itemCount; - if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first, - uri.query.afterLast) != URI_SUCCESS) { - // Failure - } - - UriQueryListA *item = queryList; - while (item) { - m_qmap[item->key] = item->value; - item = item->next; - } - uriFreeQueryListA(queryList); - } - - uriFreeUriMembersA(&uri); - - auto fraglast = (uri.query.first != NULL) ? uri.query.first : uri.fragment.afterLast; - if (uri.fragment.first != NULL && fraglast - uri.fragment.first > 0) { - m_frag = std::string(uri.fragment.first, fraglast - uri.fragment.first); - } - - m_valid = m_proto != SCHEME_NONE && (m_host.size() > 0 || m_path.size() > 0); - - if (m_valid) { - // remove userinfo from base uri - const char *start = uri.scheme.first; - if (m_userinfo != "") { - m_base = std::string(start, uri.userInfo.first - start); - start = uri.userInfo.afterLast + 1; - } - else { - m_base = std::string(""); - } - if (m_qmap.size() > 0) { - m_base += std::string(start, uri.query.first - start - 1); - } - else if (uri.fragment.first != NULL) { - m_base += std::string(start, uri.fragment.first - start - 1); - } - else if (start) { - m_base += std::string(start); - } - else { - m_base += std::string(""); - } - } - } + m_valid = false; + m_host = "none"; + m_port = -1; + m_proto = SCHEME_NONE; + m_base = suri; + m_path = ""; + m_frag = ""; + } else { + m_host = std::string(uri.hostText.first, uri.hostText.afterLast - uri.hostText.first); + + std::string prototext = std::string(uri.scheme.first, uri.scheme.afterLast - uri.scheme.first); + if (prototext == "tcp") m_proto = SCHEME_TCP; + else if (prototext == "udp") m_proto = SCHEME_UDP; + else if (prototext == "ftl") m_proto = SCHEME_FTL; + else if (prototext == "http") m_proto = SCHEME_HTTP; + else if (prototext == "ws") m_proto = SCHEME_WS; + else if (prototext == "wss") m_proto = SCHEME_WSS; + else if (prototext == "ipc") m_proto = SCHEME_IPC; + else if (prototext == "device") m_proto = SCHEME_DEVICE; + else if (prototext == "file") m_proto = SCHEME_FILE; + else if (prototext == "group") m_proto = SCHEME_GROUP; + else + m_proto = SCHEME_OTHER; + m_protostr = prototext; + + std::string porttext = std::string(uri.portText.first, uri.portText.afterLast - uri.portText.first); + m_port = atoi(porttext.c_str()); + m_userinfo = std::string(uri.userInfo.first, uri.userInfo.afterLast - uri.userInfo.first); + + for (auto h = uri.pathHead; h != NULL; h = h->next) { + auto pstr = std::string( + h->text.first, h->text.afterLast - h->text.first); + + m_path += "/"; + m_path += pstr; + m_pathseg.push_back(pstr); + } + + // string query = std::string(uri.query.first, uri.query.afterLast - uri.query.first); + if (uri.query.afterLast - uri.query.first > 0) { + UriQueryListA *queryList; + int itemCount; + if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first, + uri.query.afterLast) != URI_SUCCESS) { + // Failure + } + + UriQueryListA *item = queryList; + while (item) { + m_qmap[item->key] = item->value; + item = item->next; + } + uriFreeQueryListA(queryList); + } + + uriFreeUriMembersA(&uri); + + auto fraglast = (uri.query.first != NULL) ? uri.query.first : uri.fragment.afterLast; + if (uri.fragment.first != NULL && fraglast - uri.fragment.first > 0) { + m_frag = std::string(uri.fragment.first, fraglast - uri.fragment.first); + } + + m_valid = m_proto != SCHEME_NONE && (m_host.size() > 0 || m_path.size() > 0); + + if (m_valid) { + // remove userinfo from base uri + const char *start = uri.scheme.first; + if (m_userinfo != "") { + m_base = std::string(start, uri.userInfo.first - start); + start = uri.userInfo.afterLast + 1; + } else { + m_base = std::string(""); + } + if (m_qmap.size() > 0) { + m_base += std::string(start, uri.query.first - start - 1); + } else if (uri.fragment.first != NULL) { + m_base += std::string(start, uri.fragment.first - start - 1); + } else if (start) { + m_base += std::string(start); + } else { + m_base += std::string(""); + } + } + } } string URI::to_string() const { - return (m_qmap.size() > 0) ? m_base + "?" + getQuery() : m_base; + return (m_qmap.size() > 0) ? m_base + "?" + getQuery() : m_base; } string URI::getPathSegment(int n) const { - size_t N = (n < 0) ? m_pathseg.size()+n : n; - if (N < 0 || N >= m_pathseg.size()) return ""; - else return m_pathseg[N]; + size_t N = (n < 0) ? m_pathseg.size()+n : n; + if (N < 0 || N >= m_pathseg.size()) return ""; + else + return m_pathseg[N]; } string URI::getBaseURI(int n) const { - if (n >= (int)m_pathseg.size()) return m_base; - if (n >= 0) { - string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : ""); - for (int i=0; i<n; i++) { - r += "/"; - r += getPathSegment(i); - } - - return r; - } else if (m_pathseg.size()+n >= 0) { - string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : ""); - size_t N = m_pathseg.size()+n; - for (size_t i=0; i<N; i++) { - r += "/"; - r += getPathSegment(static_cast<int>(i)); - } - - return r; - } else return ""; + if (n >= static_cast<int>(m_pathseg.size())) return m_base; + if (n >= 0) { + string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : ""); + for (int i = 0; i < n; i++) { + r += "/"; + r += getPathSegment(i); + } + + return r; + } else if (m_pathseg.size()+n >= 0) { + string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : ""); + size_t N = m_pathseg.size()+n; + for (size_t i = 0; i < N; i++) { + r += "/"; + r += getPathSegment(static_cast<int>(i)); + } + + return r; + } else { + return ""; + } } std::string URI::getBaseURIWithUser() const { - std::string result; - - result += m_protostr + "://"; - if (m_userinfo.size() > 0) { - result += getUserInfo(); - result += "@"; - } - result += m_host; - if (m_port > 0) result += std::string(":") + std::to_string(m_port); - result += m_path; - return result; + std::string result; + + result += m_protostr + "://"; + if (m_userinfo.size() > 0) { + result += getUserInfo(); + result += "@"; + } + result += m_host; + if (m_port > 0) result += std::string(":") + std::to_string(m_port); + result += m_path; + return result; } string URI::getQuery() const { - string q; - for (auto x : m_qmap) { - if (q.length() > 0) q += "&"; - q += x.first + "=" + x.second; - } - return q; -}; + string q; + for (auto x : m_qmap) { + if (q.length() > 0) q += "&"; + q += x.first + "=" + x.second; + } + return q; +} void URI::setAttribute(const string &key, const string &value) { - m_qmap[key] = value; + m_qmap[key] = value; } void URI::setAttribute(const string &key, int value) { - m_qmap[key] = std::to_string(value); + m_qmap[key] = std::to_string(value); } void URI::to_json(nlohmann::json &json) const { - std::string uri = to_string(); - if (m_frag.size() > 0) uri += std::string("#") + getFragment(); - - json["uri"] = uri; - for (auto i : m_qmap) { - auto *current = &json; - - size_t pos = 0; - size_t lpos = 0; - while ((pos = i.first.find('/', lpos)) != std::string::npos) { - std::string subobj = i.first.substr(lpos, pos-lpos); - current = &((*current)[subobj]); - lpos = pos+1; - } - - std::string obj = i.first.substr(lpos); - - auto p = nlohmann::json::parse(i.second, nullptr, false); - if (!p.is_discarded()) { - (*current)[obj] = p; - } else { - (*current)[obj] = i.second; - } - } + std::string uri = to_string(); + if (m_frag.size() > 0) uri += std::string("#") + getFragment(); + + json["uri"] = uri; + for (auto i : m_qmap) { + auto *current = &json; + + size_t pos = 0; + size_t lpos = 0; + while ((pos = i.first.find('/', lpos)) != std::string::npos) { + std::string subobj = i.first.substr(lpos, pos-lpos); + current = &((*current)[subobj]); + lpos = pos+1; + } + + std::string obj = i.first.substr(lpos); + + auto p = nlohmann::json::parse(i.second, nullptr, false); + if (!p.is_discarded()) { + (*current)[obj] = p; + } else { + (*current)[obj] = i.second; + } + } } bool URI::hasUserInfo() const { - return m_userinfo != ""; + return m_userinfo != ""; } const std::string &URI::getUserInfo() const { - return m_userinfo; + return m_userinfo; } diff --git a/test/broadcast_unit.cpp b/test/broadcast_unit.cpp index 77a587e1f1df1e1067791c6e06ebb5128d425828..56f012eed2394648dcc6f318933e13815309832e 100644 --- a/test/broadcast_unit.cpp +++ b/test/broadcast_unit.cpp @@ -15,66 +15,66 @@ using ftl::protocol::ChannelSet; using ftl::protocol::FrameID; class BTestStream : public ftl::protocol::Stream { - public: - BTestStream() {}; - ~BTestStream() {}; - - bool post(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt) { - seen(FrameID(spkt.streamID, spkt.frame_number), spkt.channel); - trigger(spkt, pkt); - return true; - } + public: + BTestStream() {}; + ~BTestStream() {}; + + bool post(const ftl::protocol::StreamPacket &spkt, const ftl::protocol::Packet &pkt) { + seen(FrameID(spkt.streamID, spkt.frame_number), spkt.channel); + trigger(spkt, pkt); + return true; + } - bool begin() override { return true; } - bool end() override { return true; } - bool active() override { return true; } + bool begin() override { return true; } + bool end() override { return true; } + bool active() override { return true; } - void setProperty(ftl::protocol::StreamProperty opt, std::any value) override {} + void setProperty(ftl::protocol::StreamProperty opt, std::any value) override {} - std::any getProperty(ftl::protocol::StreamProperty opt) override { return 0; } + std::any getProperty(ftl::protocol::StreamProperty opt) override { return 0; } - bool supportsProperty(ftl::protocol::StreamProperty opt) override { return true; } + bool supportsProperty(ftl::protocol::StreamProperty opt) override { return true; } - void forceSeen(FrameID id, Channel channel) { + void forceSeen(FrameID id, Channel channel) { seen(id, channel); } }; TEST_CASE("ftl::stream::Broadcast()::write", "[stream]") { - std::unique_ptr<Broadcast> mux = std::make_unique<Broadcast>(); - REQUIRE(mux); - - SECTION("write with two streams") { - std::shared_ptr<Stream> s1 = std::make_shared<BTestStream>(); - REQUIRE(s1); - std::shared_ptr<Stream> s2 = std::make_shared<BTestStream>(); - REQUIRE(s2); - - mux->add(s1); - mux->add(s2); - - StreamPacket tspkt1 = {4,0,0,1,Channel::kColour}; - StreamPacket tspkt2 = {4,0,0,1,Channel::kColour}; - - auto h1 = s1->onPacket([&tspkt1](const StreamPacket &spkt, const Packet &pkt) { - tspkt1 = spkt; - return true; - }); - auto h2 = s2->onPacket([&tspkt2](const StreamPacket &spkt, const Packet &pkt) { - tspkt2 = spkt; - return true; - }); - - REQUIRE( mux->post({4,100,0,1,Channel::kColour},{}) ); - REQUIRE( tspkt1.timestamp == 100 ); - REQUIRE( tspkt2.timestamp == 100 ); - } + std::unique_ptr<Broadcast> mux = std::make_unique<Broadcast>(); + REQUIRE(mux); + + SECTION("write with two streams") { + std::shared_ptr<Stream> s1 = std::make_shared<BTestStream>(); + REQUIRE(s1); + std::shared_ptr<Stream> s2 = std::make_shared<BTestStream>(); + REQUIRE(s2); + + mux->add(s1); + mux->add(s2); + + StreamPacket tspkt1 = {4,0,0,1,Channel::kColour}; + StreamPacket tspkt2 = {4,0,0,1,Channel::kColour}; + + auto h1 = s1->onPacket([&tspkt1](const StreamPacket &spkt, const Packet &pkt) { + tspkt1 = spkt; + return true; + }); + auto h2 = s2->onPacket([&tspkt2](const StreamPacket &spkt, const Packet &pkt) { + tspkt2 = spkt; + return true; + }); + + REQUIRE( mux->post({4,100,0,1,Channel::kColour},{}) ); + REQUIRE( tspkt1.timestamp == 100 ); + REQUIRE( tspkt2.timestamp == 100 ); + } } TEST_CASE("Broadcast enable", "[stream]") { - std::unique_ptr<Broadcast> mux = std::make_unique<Broadcast>(); - REQUIRE(mux); + std::unique_ptr<Broadcast> mux = std::make_unique<Broadcast>(); + REQUIRE(mux); std::shared_ptr<BTestStream> s1 = std::make_shared<BTestStream>(); REQUIRE(s1); @@ -82,12 +82,12 @@ TEST_CASE("Broadcast enable", "[stream]") { REQUIRE(s2); mux->add(s1); - mux->add(s2); + mux->add(s2); SECTION("enable frame id") { FrameID id1(0, 1); s1->forceSeen(id1, Channel::kColour); - // s2->forceSeen(id1, Channel::kColour); + // s2->forceSeen(id1, Channel::kColour); REQUIRE( !s1->enabled(id1) ); REQUIRE( mux->enable(id1) ); @@ -111,15 +111,15 @@ TEST_CASE("Broadcast enable", "[stream]") { SECTION("enable frame id for unseen") { FrameID id(0, 1); REQUIRE( mux->enable(id) ); - REQUIRE( s1->enabled(id) ); - REQUIRE( s2->enabled(id) ); + REQUIRE( s1->enabled(id) ); + REQUIRE( s2->enabled(id) ); } SECTION("enable channel for unseen") { FrameID id(0, 1); REQUIRE( mux->enable(id, Channel::kDepth) ); - REQUIRE( s1->enabled(id, Channel::kDepth) ); - REQUIRE( s2->enabled(id, Channel::kDepth) ); + REQUIRE( s1->enabled(id, Channel::kDepth) ); + REQUIRE( s2->enabled(id, Channel::kDepth) ); } SECTION("enable channel set for unseen") { diff --git a/test/peer_unit.cpp b/test/peer_unit.cpp index 219f47c7ccfa0173e2ae28ceabe716f9dd4a5b5c..3679e6d7b954d014e4eaa9b845c20389fa01f5c9 100644 --- a/test/peer_unit.cpp +++ b/test/peer_unit.cpp @@ -34,302 +34,302 @@ static std::atomic<int> ctr_ = 0; // --- Tests ------------------------------------------------------------------- TEST_CASE("Peer(int)", "[]") { - SECTION("initiates a valid handshake") { - auto s = createMockPeer(ctr_++); - s->start(); + SECTION("initiates a valid handshake") { + auto s = createMockPeer(ctr_++); + s->start(); - LOG(INFO) << "STARTED"; + LOG(INFO) << "STARTED"; - auto [name, hs] = readResponse<ftl::net::Handshake>(0); - - REQUIRE( name == "__handshake__" ); - - // 1) Sends magic (64bits) - REQUIRE( get<0>(hs) == ftl::net::kMagic ); - - // 2) Sends FTL Version - REQUIRE( get<1>(hs) == (FTL_VERSION_MAJOR << 16) + (FTL_VERSION_MINOR << 8) + FTL_VERSION_PATCH ); - - // 3) Sends peer UUID - - - REQUIRE( s->status() == NodeStatus::kConnecting ); - } - - SECTION("completes on full handshake") { - int lidx = ctr_++; - int cidx = ctr_++; - auto s_l = createMockPeer(lidx); - auto s_c = createMockPeer(cidx); + auto [name, hs] = readResponse<ftl::net::Handshake>(0); + + REQUIRE( name == "__handshake__" ); + + // 1) Sends magic (64bits) + REQUIRE( get<0>(hs) == ftl::net::kMagic ); + + // 2) Sends FTL Version + REQUIRE( get<1>(hs) == static_cast<unsigned int>((FTL_VERSION_MAJOR << 16) + (FTL_VERSION_MINOR << 8) + FTL_VERSION_PATCH )); + + // 3) Sends peer UUID + + + REQUIRE( s->status() == NodeStatus::kConnecting ); + } + + SECTION("completes on full handshake") { + int lidx = ctr_++; + int cidx = ctr_++; + auto s_l = createMockPeer(lidx); + auto s_c = createMockPeer(cidx); - s_l->start(); - - // get sent message by s_l and place it in s_c's buffer - fakedata[cidx] = fakedata[lidx]; - s_l->data(); // listenin peer: process - // vice versa, listening peer gets reply and processes it - fakedata[lidx] = fakedata[cidx]; - s_c->data(); // connecting peer: process - sleep_for(milliseconds(50)); + s_l->start(); + + // get sent message by s_l and place it in s_c's buffer + fakedata[cidx] = fakedata[lidx]; + s_l->data(); // listenin peer: process + // vice versa, listening peer gets reply and processes it + fakedata[lidx] = fakedata[cidx]; + s_c->data(); // connecting peer: process + sleep_for(milliseconds(50)); - // both peers should be connected now - REQUIRE( s_c->status() == NodeStatus::kConnected ); - REQUIRE( s_l->status() == NodeStatus::kConnected ); - } - - SECTION("has correct version on full handshake") { - - // MockPeer s = MockPeer::create_connecting_peer(); - // Send handshake response - // send_handshake(s); - // s.mock_data(); - //sleep_for(milliseconds(50)); - // REQUIRE( (s.getFTLVersion() == (8 << 16) + (5 << 8) + 2) ); - } - - SECTION("has correct peer id on full handshake") { - // MockPeer s = MockPeer::create_connecting_peer(); - // - // Send handshake response - //REQUIRE( s.id() == ); - } + // both peers should be connected now + REQUIRE( s_c->status() == NodeStatus::kConnected ); + REQUIRE( s_l->status() == NodeStatus::kConnected ); + } + + SECTION("has correct version on full handshake") { + + // MockPeer s = MockPeer::create_connecting_peer(); + // Send handshake response + // send_handshake(s); + // s.mock_data(); + //sleep_for(milliseconds(50)); + // REQUIRE( (s.getFTLVersion() == (8 << 16) + (5 << 8) + 2) ); + } + + SECTION("has correct peer id on full handshake") { + // MockPeer s = MockPeer::create_connecting_peer(); + // + // Send handshake response + //REQUIRE( s.id() == ); + } - ftl::protocol::reset(); + ftl::protocol::reset(); } TEST_CASE("Peer::call()", "[rpc]") { - int c = ctr_++; - auto s = createMockPeer(c); - send_handshake(*s.get()); - s->data(); - sleep_for(milliseconds(50)); - - SECTION("one argument call") { - REQUIRE( s->isConnected() ); - - fakedata[c] = ""; - - // Thread to provide response to otherwise blocking call - std::thread thr([&s, c]() { - while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); - - auto [id,value] = readRPC<tuple<int>>(c); - auto res_obj = std::make_tuple(1,id,"__return__",get<0>(value)+22); - std::stringstream buf; - msgpack::pack(buf, res_obj); - fakedata[c] = buf.str(); - s->data(); - sleep_for(milliseconds(50)); - }); - int res = s->call<int>("test1", 44); - thr.join(); - - REQUIRE( (res == 66) ); - } - - SECTION("no argument call") { - REQUIRE( s->isConnected() ); - - fakedata[c] = ""; - - // Thread to provide response to otherwise blocking call - std::thread thr([&s, c]() { - while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); - - auto res = readRPC<tuple<>>(c); - auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",77); - std::stringstream buf; - msgpack::pack(buf, res_obj); - fakedata[c] = buf.str(); - s->data(); - sleep_for(milliseconds(50)); - }); - - int res = s->call<int>("test1"); - - thr.join(); - - REQUIRE( (res == 77) ); - } + int c = ctr_++; + auto s = createMockPeer(c); + send_handshake(*s.get()); + s->data(); + sleep_for(milliseconds(50)); + + SECTION("one argument call") { + REQUIRE( s->isConnected() ); + + fakedata[c] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s, c]() { + while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto [id,value] = readRPC<tuple<int>>(c); + auto res_obj = std::make_tuple(1,id,"__return__",get<0>(value)+22); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[c] = buf.str(); + s->data(); + sleep_for(milliseconds(50)); + }); + int res = s->call<int>("test1", 44); + thr.join(); + + REQUIRE( (res == 66) ); + } + + SECTION("no argument call") { + REQUIRE( s->isConnected() ); + + fakedata[c] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s, c]() { + while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto res = readRPC<tuple<>>(c); + auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",77); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[c] = buf.str(); + s->data(); + sleep_for(milliseconds(50)); + }); + + int res = s->call<int>("test1"); + + thr.join(); + + REQUIRE( (res == 77) ); + } - SECTION("vector return from call") { - REQUIRE( s->isConnected() ); - - fakedata[c] = ""; - - // Thread to provide response to otherwise blocking call - std::thread thr([&s, c]() { - while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); - - auto res = readRPC<tuple<>>(c); - vector<int> data = {44,55,66}; - auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",data); - std::stringstream buf; - msgpack::pack(buf, res_obj); - fakedata[c] = buf.str(); - s->data(); - sleep_for(milliseconds(50)); - }); - - vector<int> res = s->call<vector<int>>("test1"); - - thr.join(); - - REQUIRE( (res[0] == 44) ); - REQUIRE( (res[2] == 66) ); - } + SECTION("vector return from call") { + REQUIRE( s->isConnected() ); + + fakedata[c] = ""; + + // Thread to provide response to otherwise blocking call + std::thread thr([&s, c]() { + while (fakedata[c].size() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + auto res = readRPC<tuple<>>(c); + vector<int> data = {44,55,66}; + auto res_obj = std::make_tuple(1,std::get<0>(res),"__return__",data); + std::stringstream buf; + msgpack::pack(buf, res_obj); + fakedata[c] = buf.str(); + s->data(); + sleep_for(milliseconds(50)); + }); + + vector<int> res = s->call<vector<int>>("test1"); + + thr.join(); + + REQUIRE( (res[0] == 44) ); + REQUIRE( (res[2] == 66) ); + } - s.reset(); - ftl::protocol::reset(); + s.reset(); + ftl::protocol::reset(); } TEST_CASE("Peer::bind()", "[rpc]") { - int c = ctr_++; - auto s = createMockPeer(c); - send_handshake(*s.get()); - s->data(); - sleep_for(milliseconds(50)); - + int c = ctr_++; + auto s = createMockPeer(c); + send_handshake(*s.get()); + s->data(); + sleep_for(milliseconds(50)); + - SECTION("no argument call") { - bool done = false; - - s->bind("hello", [&]() { - done = true; - }); + SECTION("no argument call") { + bool done = false; + + s->bind("hello", [&]() { + done = true; + }); - s->send("hello"); - s->data(); // Force it to read the fake send... - sleep_for(milliseconds(50)); - - REQUIRE( done ); - } - - SECTION("one argument call") { - int done = 0; - - s->bind("hello", [&](int a) { - done = a; - }); - - s->send("hello", 55); - s->data(); // Force it to read the fake send... - sleep_for(milliseconds(50)); - - REQUIRE( (done == 55) ); - } - - SECTION("two argument call") { - std::string done; - - s->bind("hello", [&](int a, const std::string &b) { - done = b; - }); + s->send("hello"); + s->data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( done ); + } + + SECTION("one argument call") { + int done = 0; + + s->bind("hello", [&](int a) { + done = a; + }); + + s->send("hello", 55); + s->data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); + } + + SECTION("two argument call") { + std::string done; + + s->bind("hello", [&](int a, const std::string &b) { + done = b; + }); - s->send("hello", 55, "world"); - s->data(); // Force it to read the fake send... - sleep_for(milliseconds(50)); - - REQUIRE( (done == "world") ); - } + s->send("hello", 55, "world"); + s->data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == "world") ); + } - SECTION("int return value") { - int done = 0; - - s->bind("hello", [&](int a) -> int { - done = a; - return a; - }); - - s->asyncCall<int>("hello", [](int a){}, 55); - s->data(); // Force it to read the fake send... - sleep_for(milliseconds(50)); - - REQUIRE( (done == 55) ); - REQUIRE( (readRPCReturn<int>(c) == 55) ); - } + SECTION("int return value") { + int done = 0; + + s->bind("hello", [&](int a) -> int { + done = a; + return a; + }); + + s->asyncCall<int>("hello", [](int a){}, 55); + s->data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); + REQUIRE( (readRPCReturn<int>(c) == 55) ); + } - SECTION("vector return value") { - int done = 0; - - s->bind("hello", [&](int a) -> vector<int> { - done = a; - vector<int> b = {a,45}; - return b; - }); - - s->asyncCall<int>("hello", [](int a){}, 55); - s->data(); // Force it to read the fake send... - sleep_for(milliseconds(50)); - - REQUIRE( (done == 55) ); + SECTION("vector return value") { + int done = 0; + + s->bind("hello", [&](int a) -> vector<int> { + done = a; + vector<int> b = {a,45}; + return b; + }); + + s->asyncCall<int>("hello", [](int a){}, 55); + s->data(); // Force it to read the fake send... + sleep_for(milliseconds(50)); + + REQUIRE( (done == 55) ); - auto res = readRPCReturn<vector<int>>(c); - REQUIRE( (res[1] == 45) ); - } + auto res = readRPCReturn<vector<int>>(c); + REQUIRE( (res[1] == 45) ); + } - s.reset(); - ftl::protocol::reset(); + s.reset(); + ftl::protocol::reset(); } TEST_CASE("Socket::send()", "[io]") { - int c = ctr_++; - auto s = createMockPeer(c); - sleep_for(milliseconds(50)); + int c = ctr_++; + auto s = createMockPeer(c); + sleep_for(milliseconds(50)); - SECTION("send an int") { - int i = 607; - - s->send("dummy",i); - - auto [name, value] = readResponse<tuple<int>>(c); - - REQUIRE( (name == "dummy") ); - REQUIRE( (get<0>(value) == 607) ); - } - - SECTION("send a string") { - std::string str("hello world"); - s->send("dummy",str); - - auto [name, value] = readResponse<tuple<std::string>>(c); - - REQUIRE( (name == "dummy") ); - REQUIRE( (get<0>(value) == "hello world") ); - } - - SECTION("send const char* string") { - s->send("dummy","hello world"); - - auto [name, value] = readResponse<tuple<std::string>>(c); - - REQUIRE( (name == "dummy") ); - REQUIRE( (get<0>(value) == "hello world") ); - } - - SECTION("send a tuple") { - auto tup = std::make_tuple(55,66,true,6.7); - s->send("dummy",tup); - - auto [name, value] = readResponse<tuple<decltype(tup)>>(c); - - REQUIRE( (name == "dummy") ); - REQUIRE( (get<1>(get<0>(value)) == 66) ); - } - - SECTION("send multiple strings") { - std::string str("hello "); - std::string str2("world"); - s->send("dummy2",str,str2); - - auto [name, value] = readResponse<tuple<std::string,std::string>>(c); - - REQUIRE( (name == "dummy2") ); - REQUIRE( (get<0>(value) == "hello ") ); - REQUIRE( (get<1>(value) == "world") ); - } + SECTION("send an int") { + int i = 607; + + s->send("dummy",i); + + auto [name, value] = readResponse<tuple<int>>(c); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == 607) ); + } + + SECTION("send a string") { + std::string str("hello world"); + s->send("dummy",str); + + auto [name, value] = readResponse<tuple<std::string>>(c); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == "hello world") ); + } + + SECTION("send const char* string") { + s->send("dummy","hello world"); + + auto [name, value] = readResponse<tuple<std::string>>(c); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<0>(value) == "hello world") ); + } + + SECTION("send a tuple") { + auto tup = std::make_tuple(55,66,true,6.7); + s->send("dummy",tup); + + auto [name, value] = readResponse<tuple<decltype(tup)>>(c); + + REQUIRE( (name == "dummy") ); + REQUIRE( (get<1>(get<0>(value)) == 66) ); + } + + SECTION("send multiple strings") { + std::string str("hello "); + std::string str2("world"); + s->send("dummy2",str,str2); + + auto [name, value] = readResponse<tuple<std::string,std::string>>(c); + + REQUIRE( (name == "dummy2") ); + REQUIRE( (get<0>(value) == "hello ") ); + REQUIRE( (get<1>(value) == "world") ); + } - s.reset(); - ftl::protocol::reset(); + s.reset(); + ftl::protocol::reset(); }