/* * This file is part of the Flowee project * Copyright (C) 2016,2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef FLOWEE_NETWORKCONNECTION_H #define FLOWEE_NETWORKCONNECTION_H #include "NetworkEndPoint.h" #include #include #include class NetworkManager; class NetworkManagerConnection; class Message; /** * An instance gives access to a client-server connection. * The NetworkManager has getters to create NetworkConnection instances, once * one is successfully created you can use it like a remote server that you can * post messages to, get messages from (using setOnIncomingMessage()) and it will * automatically reconnect and resend messages on failures. * * The NetworkConnection is a very light-weight class for * easy access to a certain connection. It is used as a value-class and as such it has * an isValid() method to allow checking if the connection it represents still exists. * For instance when the NetworkManager that owns all the connections is deleted, this * will return false. * * Callbacks are registered with one instance of a NetworkConnection, and when that * instance goes out of scope the callbacks will be destructed as well. * Simplest usage is thus something like this; * @code class MyServiceHandler { public: MyServiceHandler(NetworkConnection && connection) : m_connection(std::move(connection)) { m_connection.setOnConnected(std::bind(&MyServiceHandler::onConnected, this)); m_connection.setOnDisconnected(std::bind(&MyServiceHandler::onDisconnected, this)); m_connection.setOnIncomingMessage(std::bind(&MyServiceHandler::onIncomingMessage, this, std::placeholders::_1)); } ~MyServiceHandler() { m_connection.shutdown(); } private: void onConnected(); void onDisconnected(); void onIncomingMessage(const Message &message); private: NetworkConnection m_connection; }; @endcode */ class NetworkConnection { public: /// create an invalid default connection object. NetworkConnection(); /** * Create a NetworkConnection representing a certain connection-id. * This constructor can be used to create a connection from a Message by using * the Message::remote member as the \a id. */ NetworkConnection(NetworkManager *parent, int id); /// called by the NetworkManager NetworkConnection(std::shared_ptr &parent, int id); NetworkConnection(NetworkConnection && other); ~NetworkConnection(); /// returns true if we have an actual socket connection to the remote() bool isConnected() const; /// initiates a (re)connect void connect(); /** * Will sever the connection, close the socket. This may have additional effects, read on: * * The lifetime management of a network connection has two modes, either it is an * application-initiated outgoing connection. Or it is an incoming connection. * * For incoming connections 'disconnect()' is identical to shutdown(). * * For application intitiated connections the lifetime is set by the application holding * instances of this class: NetworkConnection, representing a underlying connection. * While there can be many NetworkConnection instances all pointing to a single * underlying connection, when the refcount drops to zero we will garbage collect * the underlying connection and call shutdown(). * * Just calling disconnect on an outgoing connection will thus allow you to call connect() * later and all callbacks stay intact. All messages that were scheduled to be sent are * deleted, but calling send() will initiate a re-connect. * * @see shutdown */ void disconnect(); /** * Drops all callbacks, causing the underlying connection to be deleted. * This will, eventually, make this object not isValid() */ void shutdown(); /// Return the current remote we are connected to, or an empty one if we are not connected EndPoint endPoint() const; inline int connectionId() const { return m_id; } enum AcceptLimit { AcceptConnection, AcceptForLogin }; /// accepts an incoming connection void accept(AcceptLimit cl = AcceptConnection); enum MessagePriority { NormalPriority, HighPriority }; /// send a message, automatically connecting if needed. /// Throws NetworkException in case message was malformed /// Throws NetworkQueueFullError in case there is no space void send(const Message &message, MessagePriority priority = NormalPriority); /// return true if this object can be operated on and represents a real connection bool isValid() const; /** * Clears the networkconnection and all related callbacks. * isValid() will return false after this is called. */ void clear(); NetworkConnection& operator=(NetworkConnection && other); /** * Sets a callback to be called every time we (re)connect at the socket level. * Notice that callbacks are owned by the connection object, the callback will be * garbage collected when the instance of the NetworkConnection goes out of scope. * You can have only one callback per NetworkConnection instance, but you can have * many instances representing the same physical connection. */ void setOnConnected(const std::function &callback); /** * Sets a callback to be called every time we have a disconnect. * Notice that callbacks are owned by the connection object, the callback will be * garbage collected when the instance of the NetworkConnection goes out of scope. * You can have only one callback per NetworkConnection instance, but you can have * many instances representing the same physical connection. */ void setOnDisconnected(const std::function &callback); /** * Sets a callback for incoming messages, one message per call. * Notice that callbacks are owned by the connection object, the callback will be * garbage collected when the instance of the NetworkConnection goes out of scope. * You can have only one callback per NetworkConnection instance, but you can have * many instances representing the same physical connection. */ void setOnIncomingMessage(const std::function &callback); /** * Sets a callback for errors on the connection. * The callback takes two arguments, an int and a boost::system::error_code. * The int is the connectionId() this error happened on, and the errorcode is * what we get from boost::asio. */ void setOnError(const std::function &callback); /** * Set a login-message-creator method to this outgoing connection. * * Servers that use something like the ConnectionAuthorizer will want * the first message on a new connection to be the login message, or * the connection will be broken. * Setting a creator here will make this connection send it on * connect, ensuring its the first message on the socket regardless of * what is in the message-queue. * * Please note that if there are multiple NetworkConnection objects for * a single underlying connection, the last set creator will be used. */ void setLoginMessageCreator(const std::function &creator); /** * Punish a node that misbehaves (for instance if it breaks your protocol). * A node that gathers a total of 1000 points is banned for 24 hours, * every hour 100 points are subtracted from a each node's punishment-score. */ void punishPeer(int punishment); /** * Posts a task on the underlying strand for it to be executed serially. */ void postOnStrand(const std::function &task); /** * @brief setMessageHeaderLegacy marks this peer to return legacy-p2p style messages. * Envelope handling will happen using a different algo. * @param on Set to true when using to connect to the p2p network. */ void setMessageHeaderLegacy(bool on); /** * @brief setMessageQueueSize allows a pre-connect configuration of how many buffers this connection should have. * @param main the amount of messages we queue. The variable should be positive and fit in a `short` integer. * @param priority the amount of priority-messages we queue. The variable should be >= 3 and fit in a `short` integer. * * Notice that this should be called before connect() or send() to be honored. */ void setMessageQueueSizes(int main, int priority); /** * @brief setCertificate called on encrypted, outgoing connection allows validation during handshake. */ void setCertificate(const Streaming::ConstBuffer &buffer); private: NetworkConnection(const NetworkConnection&); NetworkConnection& operator=(NetworkConnection&); void dummy() const; std::weak_ptr m_parent; int m_id; int m_callbacksId; }; #endif