From 527f58d3537b5d961d83ff6907c86ca0d18107fc Mon Sep 17 00:00:00 2001 From: Henning Stahlke Date: Mon, 23 Nov 2020 02:57:25 +0100 Subject: [PATCH] Refactored CPDLP. --- src/MultiPlayer/CMakeLists.txt | 8 +- src/MultiPlayer/cpdlc.cxx | 182 ++++++++++++++++++ src/MultiPlayer/cpdlc.hxx | 82 +++++++++ src/MultiPlayer/mpirc.cxx | 304 +++++++++++++++++++++++++++++++ src/MultiPlayer/mpirc.hxx | 105 +++++++++++ src/MultiPlayer/multiplaymgr.cxx | 133 +++++++++++++- src/MultiPlayer/multiplaymgr.hxx | 16 +- 7 files changed, 819 insertions(+), 11 deletions(-) create mode 100644 src/MultiPlayer/cpdlc.cxx create mode 100644 src/MultiPlayer/cpdlc.hxx create mode 100644 src/MultiPlayer/mpirc.cxx create mode 100644 src/MultiPlayer/mpirc.hxx diff --git a/src/MultiPlayer/CMakeLists.txt b/src/MultiPlayer/CMakeLists.txt index c2d0f59b5..5dd6ea575 100644 --- a/src/MultiPlayer/CMakeLists.txt +++ b/src/MultiPlayer/CMakeLists.txt @@ -3,13 +3,17 @@ include(FlightGearComponent) set(SOURCES multiplaymgr.cxx tiny_xdr.cxx - MPServerResolver.cxx + MPServerResolver.cxx + mpirc.cxx + cpdlc.cxx ) set(HEADERS multiplaymgr.hxx tiny_xdr.hxx - MPServerResolver.hxx + MPServerResolver.hxx + mpirc.hxx + cpdlc.hxx ) flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}") diff --git a/src/MultiPlayer/cpdlc.cxx b/src/MultiPlayer/cpdlc.cxx new file mode 100644 index 000000000..6a534ea23 --- /dev/null +++ b/src/MultiPlayer/cpdlc.cxx @@ -0,0 +1,182 @@ +////////////////////////////////////////////////////////////////////// +// +// cpdlc.cxx +// +// started November 2020 +// Authors: Michael Filhol, Henning Stahlke +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ +// +////////////////////////////////////////////////////////////////////// + +#include "cpdlc.hxx" +#include
+ +/////////////////////////////////////////////////////////////////////////////// +// CPDLCManager +/////////////////////////////////////////////////////////////////////////////// + +CPDLCManager::CPDLCManager(IRCConnection* irc) : + _irc(irc) +{ + // CPDLC link status props + _pStatus = fgGetNode("/network/cpdlc/link/status", true); + _pStatus->setIntValue(_status); + _pDataAuthority = fgGetNode("/network/cpdlc/link/data-authority", true); + _pDataAuthority->setStringValue(_data_authority); + // CPDLC message output + _pMessage = fgGetNode("/network/cpdlc/rx/message", true); + _pMessage->setStringValue(""); + _pNewMessage = fgGetNode("/network/cpdlc/rx/new-message", true); + _pNewMessage->setBoolValue(0); +} + +CPDLCManager::~CPDLCManager() +{ +} + +/* +connect will be called by +1) user via a fgcommand defined in multiplaymgr +2) CPDLC::update() while waiting for IRC to become ready + +authority parameter is accepted only when coming from disconnected state, +disconnect must be called first if user wants to change authority (however, ATC +handover is not blocked by this) + +IRC connection will be initiated if necessary, it takes a while to connect to IRC +which is why connect is called from update() when in state CPDLC_WAIT_IRC_READY +*/ +bool CPDLCManager::connect(const std::string authority = "") +{ + // ensure we get an authority on first call but do not accept a change before + // resetting _data_authority in disconnect() + if (!_data_authority.size()) { + if (!authority.size()) { + SG_LOG(SG_NETWORK, SG_WARN, "cpdlcConnect not possible: empty argument!"); + return false; + } else { + _data_authority = authority; + } + } + if (authority.size() && authority != _data_authority) { + SG_LOG(SG_NETWORK, SG_WARN, "cpdlcConnect: cannot change authority now, use disconnect first!"); + return false; + } + + // launch IRC connection as needed + if (!_irc->isConnected()) { + SG_LOG(SG_NETWORK, SG_DEV_WARN, "Connecting to IRC server..."); + if (!_irc->login()) { + return false; + } + _status = CPDLC_WAIT_IRC_READY; + return true; + } else if (_irc->isReady() && _status != CPDLC_ONLINE) { + SG_LOG(SG_NETWORK, SG_DEV_WARN, "CPDLC send connect"); + _status = CPDLC_CONNECTING; + _pStatus->setIntValue(_status); + return _irc->sendPrivmsg(_data_authority, CPDLC_MSGPREFIX_CONNECT); + } + return false; +} + +void CPDLCManager::disconnect() +{ + if (_irc && _irc->isConnected() && _data_authority.size()) { + _irc->sendPrivmsg(_data_authority, CPDLC_MSGPREFIX_DISCONNECT); + } + _data_authority = ""; + _pDataAuthority->setStringValue(_data_authority); + _status = CPDLC_OFFLINE; + _pStatus->setIntValue(_status); +} + +bool CPDLCManager::send(const std::string message) +{ + std::string textline(CPDLC_MSGPREFIX_MSG); // TODO surely a way to format std string in line + textline += ' '; + textline += message; + return _irc->sendPrivmsg(_data_authority, textline); +} + +// move next message from input queue to property tree +void CPDLCManager::getMessage() +{ + if (!_incoming_messages.empty()) { + struct IRCMessage entry = _incoming_messages.front(); + _incoming_messages.pop_front(); + _pMessage->setStringValue(entry.textline.substr(CPDLC_MSGPREFIX_MSG.length() + 1)); + if (_incoming_messages.empty()) { + _pNewMessage->setBoolValue(0); + } + } +} + +/* + update() call this regularly to complete connect and check for new messages + update frequency requirement should be low (e.g. once per second) but frame rate + will not hurt as the code is slim +*/ +void CPDLCManager::update() +{ + if (_irc) { + if (_irc->isConnected()) { + if (_status == CPDLC_WAIT_IRC_READY && _irc->isReady()) { + SG_LOG(SG_NETWORK, SG_DEV_WARN, "CPDLC IRC ready, connecting..."); + connect(); + } + if (_irc->hasMessage()) { + processMessage(_irc->getMessage()); + } + } + // IRC disconnected (unexpectedly) + else if (_status != CPDLC_OFFLINE) { + _status = CPDLC_OFFLINE; + _pStatus->setIntValue(_status); + } + } +} + +// process incoming message +void CPDLCManager::processMessage(struct IRCMessage message) +{ + SG_LOG(SG_NETWORK, SG_INFO, "CPDLC message"); + // connection accepted by ATC, or new data authority (been transferred) + if (message.textline.find(CPDLC_MSGPREFIX_CONNECT) == 0) { + _data_authority = message.sender; + _pDataAuthority->setStringValue(_data_authority); // make this known to ACFT + _status = CPDLC_ONLINE; + _pStatus->setIntValue(_status); + } + // do not process message if sender does not match our current data authority + if (message.sender != _data_authority) { + SG_LOG(SG_NETWORK, SG_WARN, "Received CPDLC message from foreign authority."); + return; + } + // connection rejected, or terminated by ATC + if (message.textline.find(CPDLC_MSGPREFIX_DISCONNECT) == 0) { + if (message.sender == _data_authority) { + disconnect(); + } + } + // store valid message in queue for later retrieval by aircraft + else if (message.textline.find(CPDLC_MSGPREFIX_MSG) == 0) { + _incoming_messages.push_back(message); + _pNewMessage->setBoolValue(1); + } +} diff --git a/src/MultiPlayer/cpdlc.hxx b/src/MultiPlayer/cpdlc.hxx new file mode 100644 index 000000000..8a26011cf --- /dev/null +++ b/src/MultiPlayer/cpdlc.hxx @@ -0,0 +1,82 @@ +////////////////////////////////////////////////////////////////////// +// +// cpdlc.hxx +// +// started November 2020 +// Authors: Henning Stahlke, Michael Filhol +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ +// +////////////////////////////////////////////////////////////////////// + +#ifndef CPDLC_H +#define CPDLC_H + +#include +#include + +#include +#include +#include +#include +#include "mpirc.hxx" + +const std::string CPDLC_IRC_SERVER {"mpirc.flightgear.org"}; +const std::string CPDLC_MSGPREFIX_CONNECT {"___CPDLC_CONNECT___"}; +const std::string CPDLC_MSGPREFIX_MSG {"___CPDLC_MSG___"}; +const std::string CPDLC_MSGPREFIX_DISCONNECT {"___CPDLC_DISCONNECT___"}; + + +enum CPDLCStatus { + CPDLC_OFFLINE, + CPDLC_CONNECTING, + CPDLC_ONLINE, + +// private (not exposed to property tree) below this line: + CPDLC_WAIT_IRC_READY, +}; + +// +// CPDLCManager implements a ControllerPilotDataLinkConnection via an IRC connection +// +class CPDLCManager +{ +public: + CPDLCManager(IRCConnection* irc); + ~CPDLCManager(); + + bool connect(const std::string authority); + void disconnect(); + bool send(const std::string message); + void getMessage(); + void update(); + +private: + IRCConnection *_irc; + std::string _data_authority {""}; + std::deque _incoming_messages; + CPDLCStatus _status {CPDLC_OFFLINE}; + + SGPropertyNode *_pStatus {nullptr}; + SGPropertyNode *_pDataAuthority {nullptr}; + SGPropertyNode *_pMessage {nullptr}; + SGPropertyNode *_pNewMessage {nullptr}; + + void processMessage(struct IRCMessage entry); +}; + +#endif \ No newline at end of file diff --git a/src/MultiPlayer/mpirc.cxx b/src/MultiPlayer/mpirc.cxx new file mode 100644 index 000000000..20506b05c --- /dev/null +++ b/src/MultiPlayer/mpirc.cxx @@ -0,0 +1,304 @@ +////////////////////////////////////////////////////////////////////// +// +// mpirc.cxx +// +// started November 2020 +// Authors: Michael Filhol, Henning Stahlke +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ +// +////////////////////////////////////////////////////////////////////// + +#include "mpirc.hxx" +#include
+ +const std::string IRC_TEST_CHANNEL{"#mptest"}; // for development + +IRCConnection::IRCConnection(const std::string nickname, const std::string servername, const std::string port) : SGSocket(servername, port, "tcp"), + _nickname(nickname) +{ +} + +IRCConnection::~IRCConnection() +{ +} + +// setup properties to reflect the status of this IRC connection in the prop tree +void IRCConnection::setupProperties(std::string path) +{ + if (path.back() != '/') path.push_back('/'); + if (!_pReadyFlag) _pReadyFlag = fgGetNode(path + "irc-ready", true); + _pReadyFlag->setBoolValue(_logged_in); + + if (!_pMessageCountIn) _pMessageCountIn = fgGetNode(path + "msg-count-in", true); + if (!_pMessageCountOut) _pMessageCountOut = fgGetNode(path + "msg-count-out", true); + if (!_pIRCReturnCode) _pIRCReturnCode = fgGetNode(path + "last-return-code", true); +} + + +bool IRCConnection::login(const std::string nickname) +{ + if (!_connected && !connect()) { + return false; + } + if (nickname.length()) { + _nickname = nickname; + } else { + return false; + } + + std::string lines("NICK "); + lines += _nickname; + lines += IRC_MSG_TERMINATOR; + lines += "USER "; + lines += _nickname; //IRC + lines += " 0 * :"; //IRC + lines += _nickname; //IRC + lines += IRC_MSG_TERMINATOR; + return writestring(lines.c_str()); +} + +// login with nickname given to constructor +bool IRCConnection::login() +{ + return login(_nickname); +} + +// the polite way to leave +void IRCConnection::quit() +{ + if (!_connected) return; + writestring("QUIT goodbye\r\n"); + disconnect(); +} + +bool IRCConnection::sendPrivmsg(const std::string recipient, const std::string textline) +{ + if (!_logged_in) return false; + std::string line("PRIVMSG "); + line += recipient; + line += " :"; + line += textline; + line += IRC_MSG_TERMINATOR; + if (writestring(line.c_str())) { + if (_pMessageCountOut) _pMessageCountOut->setIntValue(_pMessageCountOut->getIntValue() + 1); + if (_pIRCReturnCode) _pIRCReturnCode->setStringValue(""); + return true; + } else { + return false; + } +} + +// join an IRC channel +bool IRCConnection::join(const std::string channel) +{ + if (!_logged_in) return false; + std::string lines("JOIN "); + lines += channel; + lines += IRC_MSG_TERMINATOR; + return writestring(lines.c_str()); +} + +// leave an IRC channel +bool IRCConnection::part(const std::string channel) +{ + if (!_logged_in) return false; + std::string lines("PART "); + lines += channel; + lines += IRC_MSG_TERMINATOR; + return writestring(lines.c_str()); +} + +/* + Call update() regularly to maintain connection (ping/pong) and process messages. + For information only: + The ping timeout appears to depend on the server settings and can be in the order + of minutes. However, for smooth message processing the update frequency should be + at least a few times per second and calling this at frame rate should not hurt. +*/ +void IRCConnection::update() +{ + if (_connected && readline(_read_buffer, sizeof(_read_buffer) - 1) > 0) { + std::string line(_read_buffer); // TODO: buffer size check required? + parseReceivedLine(line); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// private methods +/////////////////////////////////////////////////////////////////////////////// + +// open a connection to IRC server +bool IRCConnection::connect() +{ + if (_connected) return false; + + _connected = open(SG_IO_OUT); + if (_connected) { + nonblock(); + } else { + disconnect(); + SG_LOG(SG_NETWORK, SG_WARN, "IRCConnection::connect error"); + } + return _connected; +} + +void IRCConnection::disconnect() +{ + _logged_in = false; + if (_pReadyFlag) _pReadyFlag->setBoolValue(_logged_in); + + if (_connected) { + _connected = false; + close(); + SG_LOG(SG_NETWORK, SG_INFO, "IRCConnection::disconnect"); + } +} +void IRCConnection::pong(const std::string recipient) +{ + if (!_connected) return; + std::string line("PONG "); + line += recipient; + line += IRC_MSG_TERMINATOR; + writestring(line.c_str()); +} + +bool IRCConnection::parseReceivedLine(std::string line) +{ + /* + https://tools.ietf.org/html/rfc2812#section-3.7.2 + 2.3.1 Message format in Augmented BNF + + The protocol messages must be extracted from the contiguous stream of + octets. The current solution is to designate two characters, CR and + LF, as message separators. Empty messages are silently ignored, + which permits use of the sequence CR-LF between messages without + extra problems. + + The extracted message is parsed into the components , + and list of parameters (). + + The Augmented BNF representation for this is: + + message = [ ":" prefix SPACE ] command [ params ] crlf + prefix = servername / ( nickname [ [ "!" user ] "@" host ] ) + command = 1*letter / 3digit + params = *14( SPACE middle ) [ SPACE ":" trailing ] + =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ] + + nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF + ; any octet except NUL, CR, LF, " " and ":" + middle = nospcrlfcl *( ":" / nospcrlfcl ) + trailing = *( ":" / " " / nospcrlfcl ) + + SPACE = %x20 ; space character + crlf = %x0D %x0A ; "carriage return" "linefeed" + + */ + + // removes trailing '\r\n' + // TODO: for length(IRC_MSG_TERMINATOR) do line.pop_back(); + line.pop_back(); + line.pop_back(); + + std::string prefix; + std::string command; + std::string params; + + std::size_t pos = line.find(" ", 1); + //prefix + if (line.at(0) == ':') { + prefix = line.substr(1, pos - 1); // remove leading ":" + std::size_t end = line.find(" ", pos + 1); + command = line.substr(pos + 1, end - pos - 1); + pos = end; + } else { + command = line.substr(0, pos); + } + params = line.substr(pos + 1); + + // uncomment next line for debug output + //cout << "[prefix]" << prefix << "[cmd]" << command << "[params]" << params << "[end]" << endl; + + // receiving a message + if (command == "PRIVMSG") { + if (_pMessageCountIn) _pMessageCountIn->setIntValue(_pMessageCountIn->getIntValue() + 1); + std::string recipient = params.substr(0, params.find(" :")); + // direct private message + if (recipient == _nickname) { + struct IRCMessage rcv; + rcv.sender = prefix.substr(0, prefix.find("!")); + rcv.textline = params.substr(params.find(":") + 1); + _incoming_private_messages.push_back(rcv); + } else { + // Most likely from an IRC channel if we joined any. In this case + // recipient equals channel name (e.g. "#mptest"). IRC channel + // support could be implemented here in future. + SG_LOG(SG_NETWORK, SG_DEV_WARN, "Ignoring PRIVMSG to '" + recipient + "' (should be '" + _nickname + "')"); + } + } else if (command == "PING") { + // server pings us + std::string server = params.substr(0, params.find(" ")); + pong(server); + } else if (command == "JOIN") { + // server acks our join request + std::string channel = params.substr(0, params.find(" ")); + SG_LOG(SG_NETWORK, SG_DEV_WARN, "Joined IRC channel " + channel); //DEBUG + } else if (command == IRC_RPL_WELCOME) { + // after welcome we are logged in and allowed to send commands/messages to the IRC + _logged_in = true; + if (_pReadyFlag) _pReadyFlag->setBoolValue(1); + + //joining channel might help while development, maybe removed later + //join(IRC_TEST_CHANNEL); + } + else if (command == IRC_RPL_MOTD) { + } + else if (command == IRC_RPL_MOTDSTART) { + } + else if (command == IRC_RPL_ENDOFMOTD) { + } + else if (command == IRC_ERR_NOSUCHNICK) { + // server return code if we send to invalid nickname + if (_pIRCReturnCode) _pIRCReturnCode->setStringValue(IRC_ERR_NOSUCHNICK); + } + else if (command == "ERROR") { + if (_pIRCReturnCode) _pIRCReturnCode->setStringValue(params); + disconnect(); + } + // unexpected IRC message + else { + //SG_LOG(SG_NETWORK, SG_MANDATORY_INFO, "Unhandled IRC message "); //DEBUG + //cout << "[prefix]" << prefix << "[cmd]" << command << "[params]" << params << "[end]" << endl; + + // TODO: anything sensitive here that we should handle? + // e.g. IRC user has disconnected and username == {current-cpdlc-authority} + } + return true; +} + +IRCMessage IRCConnection::getMessage() +{ + struct IRCMessage entry { + "", "" + }; + if (!_incoming_private_messages.empty()) { + entry = _incoming_private_messages.front(); + _incoming_private_messages.pop_front(); + } + return entry; +} diff --git a/src/MultiPlayer/mpirc.hxx b/src/MultiPlayer/mpirc.hxx new file mode 100644 index 000000000..d3b2f5fca --- /dev/null +++ b/src/MultiPlayer/mpirc.hxx @@ -0,0 +1,105 @@ +////////////////////////////////////////////////////////////////////// +// +// mpirc.hxx +// +// started November 2020 +// Authors: Henning Stahlke, Michael Filhol +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ +// +////////////////////////////////////////////////////////////////////// + +#ifndef MPIRC_H +#define MPIRC_H + +#include +#include + +#include +#include +#include +#include + +const std::string IRC_DEFAULT_PORT {"6667"}; +const std::string IRC_MSG_TERMINATOR {"\r\n"}; +// https://www.alien.net.au/irc/irc2numerics.html +const std::string IRC_RPL_WELCOME {"001"}; +const std::string IRC_RPL_YOURID {"042"}; +const std::string IRC_RPL_MOTD {"372"}; +const std::string IRC_RPL_MOTDSTART {"375"}; +const std::string IRC_RPL_ENDOFMOTD {"376"}; +const std::string IRC_ERR_NOSUCHNICK {"401"}; + +const int IRC_BUFFER_SIZE = 1024; + +struct IRCMessage { + std::string sender; + std::string textline; +}; + +/* + IRCConnection implements a basic IRC client for transmitting and receiving + private messages via an IRC server + + In general it is possible to have multiple instances of this class but you + have to consider the following points: + - you cannot connect to a server with the same nickname more than once at a time + - if you want to expose status info to the property tree, you have to pass + an unique prefix to setupProperties() per instance +*/ +class IRCConnection : SGSocket +{ +public: + IRCConnection(const std::string nickname, const std::string servername, const std::string port = IRC_DEFAULT_PORT); + ~IRCConnection(); + + void setupProperties(const std::string path); + void update(); + + bool login(const std::string nickname); + bool login(); + void quit(); + + bool sendPrivmsg(const std::string recipient, const std::string textline); + bool join(const std::string channel); + bool part(const std::string channel); + + + bool isConnected() const { return _connected; } + bool isReady() const { return _logged_in; } + bool hasMessage() const { return !_incoming_private_messages.empty(); } + IRCMessage getMessage(); + +private: + bool connect(); + void disconnect(); + void pong(const std::string recipient); + bool parseReceivedLine(std::string irc_line); + + bool _connected {false}; // TCP session ok + bool _logged_in {false}; // IRC login completed + std::string _nickname {""}; + char _read_buffer[IRC_BUFFER_SIZE]; + std::deque _incoming_private_messages; + + SGPropertyNode *_pReadyFlag {nullptr}; + SGPropertyNode *_pMessageCountIn {nullptr}; + SGPropertyNode *_pMessageCountOut {nullptr}; + SGPropertyNode *_pIRCReturnCode {nullptr}; +}; + +#endif \ No newline at end of file diff --git a/src/MultiPlayer/multiplaymgr.cxx b/src/MultiPlayer/multiplaymgr.cxx index 89d3ed0c9..84b8b9273 100644 --- a/src/MultiPlayer/multiplaymgr.cxx +++ b/src/MultiPlayer/multiplaymgr.cxx @@ -33,16 +33,17 @@ #include #include #include +#include -#include -#include -#include #include +#include +#include +#include #include #include #include #include -#include +#include #include #include @@ -59,6 +60,7 @@ #endif using namespace std; + #define MAX_PACKET_SIZE 1200 #define MAX_TEXT_SIZE 768 // Increased for 2017.3 to allow for long Emesary messages. /* @@ -943,6 +945,86 @@ do_multiplayer_refreshserverlist (const SGPropertyNode * arg, SGPropertyNode * r return true; } +////////////////////////////////////////////////////////////////////// +// +// CPDLC commands: cpdlc-connect, cpdlc-send-msg, cpdlc-disconnect +// +////////////////////////////////////////////////////////////////////// + +static bool do_cpdlc_connect(const SGPropertyNode* arg, SGPropertyNode* root) +{ + FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp"); + if (!self) { + SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); + return false; + } + + // check for atc argument + std::string authority = arg->getStringValue("atc"); + // otherwise see if we got a property name to read out + if (!authority.size()) { + std::string name = arg->getStringValue("property"); + if (name.size()) { + SGPropertyNode* pNode = globals->get_props()->getNode(name); + if (!pNode) { return false; } + authority = pNode->getStringValue(); + } + } + + if (self->getCPDLC()) { + return self->getCPDLC()->connect(authority); + } + return false; +} + +static bool do_cpdlc_send_msg(const SGPropertyNode* arg, SGPropertyNode* root) +{ + FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp"); + if (!self) { + SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); + return false; + } + + // check for message argument + std::string message = arg->getStringValue("message"); + // otherwise see if we got a property name to read out + if (!message.size()) { + std::string name = arg->getStringValue("property"); + if (name.size()) { + SGPropertyNode* pNode = globals->get_props()->getNode(name); + if (!pNode) { return false; } + message = pNode->getStringValue(); + } + } + if (self->getCPDLC()) { + return self->getCPDLC()->send(message); + } + return false; +} + +static bool do_cpdlc_next_msg(const SGPropertyNode* arg, SGPropertyNode* root) +{ + FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp"); + if (!self) { + SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); + return false; + } + if (self->getCPDLC()) self->getCPDLC()->getMessage(); + return true; +} + +static bool do_cpdlc_disconnect(const SGPropertyNode* arg, SGPropertyNode* root) +{ + FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp"); + if (!self) { + SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available."); + return false; + } + if (self->getCPDLC()) self->getCPDLC()->disconnect(); + return true; +} + + ////////////////////////////////////////////////////////////////////// // // MultiplayMgr constructor @@ -956,6 +1038,13 @@ FGMultiplayMgr::FGMultiplayMgr() globals->get_commands()->addCommand("multiplayer-connect", do_multiplayer_connect); globals->get_commands()->addCommand("multiplayer-disconnect", do_multiplayer_disconnect); globals->get_commands()->addCommand("multiplayer-refreshserverlist", do_multiplayer_refreshserverlist); + + globals->get_commands()->addCommand("cpdlc-connect", do_cpdlc_connect); + globals->get_commands()->addCommand("cpdlc-send", do_cpdlc_send_msg); + globals->get_commands()->addCommand("cpdlc-next-message", do_cpdlc_next_msg); + globals->get_commands()->addCommand("cpdlc-disconnect", do_cpdlc_disconnect); + + pXmitLen = fgGetNode("/sim/multiplay/last-xmit-packet-len", true); pProtocolVersion = fgGetNode("/sim/multiplay/protocol-version", true); pMultiPlayDebugLevel = fgGetNode("/sim/multiplay/debug-level", true); @@ -963,6 +1052,8 @@ FGMultiplayMgr::FGMultiplayMgr() pMultiPlayRange = fgGetNode("/sim/multiplay/visibility-range-nm", true); pMultiPlayRange->setIntValue(100); pReplayState = fgGetNode("/sim/replay/replay-state", true); + + } // FGMultiplayMgr::FGMultiplayMgr() ////////////////////////////////////////////////////////////////////// @@ -976,6 +1067,11 @@ FGMultiplayMgr::~FGMultiplayMgr() globals->get_commands()->removeCommand("multiplayer-connect"); globals->get_commands()->removeCommand("multiplayer-disconnect"); globals->get_commands()->removeCommand("multiplayer-refreshserverlist"); + + globals->get_commands()->removeCommand("cpdlc-connect"); + globals->get_commands()->removeCommand("cpdlc-send"); + globals->get_commands()->removeCommand("cpdlc-next-message"); + globals->get_commands()->removeCommand("cpdlc-disconnect"); } // FGMultiplayMgr::~FGMultiplayMgr() ////////////////////////////////////////////////////////////////////// @@ -1080,6 +1176,16 @@ FGMultiplayMgr::init (void) // multiplayer depends on AI module fgSetBool("/sim/ai/enabled", true); } + + // MP IRC CONNECTION SETUP + std::string host = fgHasNode(MPIRC_SERVER_HOST_PROPERTY) ? fgGetString(MPIRC_SERVER_HOST_PROPERTY) : MPIRC_SERVER_HOST_DEFAULT; + std::string port = fgHasNode(MPIRC_SERVER_PORT_PROPERTY) ? fgGetString(MPIRC_SERVER_PORT_PROPERTY) : IRC_DEFAULT_PORT; + SG_LOG(SG_NETWORK, SG_DEBUG, "Creating socket to MP IRC service " + host + " on port " + port); + + _mpirc = std::make_unique(MPIRC_NICK_PREFIX + mCallsign, host, port); + _mpirc->setupProperties("/network/mpirc/"); + + _cpdlc = std::make_unique(_mpirc.get()); } // FGMultiplayMgr::init() ////////////////////////////////////////////////////////////////////// @@ -1113,7 +1219,13 @@ FGMultiplayMgr::shutdown (void) } mInitialised = false; -} // FGMultiplayMgr::Close(void) + + if (_cpdlc) _cpdlc->disconnect(); + if (_mpirc) _mpirc->quit(); + + _cpdlc.reset(); + _mpirc.reset(); +} // FGMultiplayMgr::shutdown(void) ////////////////////////////////////////////////////////////////////// void @@ -1827,7 +1939,7 @@ int FGMultiplayMgr::GetMsg(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress) ////////////////////////////////////////////////////////////////////// // -// Name: ProcessData +// Name: update // Description: Processes data waiting at the receive socket. The // processing ends when there is no more data at the socket. // @@ -1926,7 +2038,14 @@ FGMultiplayMgr::update(double dt) } else ++it; } -} // FGMultiplayMgr::ProcessData(void) + + if (_mpirc) { + _mpirc->update(); + } + if (_cpdlc) { + _cpdlc->update(); + } +} // FGMultiplayMgr::update(void) ////////////////////////////////////////////////////////////////////// void FGMultiplayMgr::ClearMotion() diff --git a/src/MultiPlayer/multiplaymgr.hxx b/src/MultiPlayer/multiplaymgr.hxx index daf5a91e6..6b2a36ef0 100644 --- a/src/MultiPlayer/multiplaymgr.hxx +++ b/src/MultiPlayer/multiplaymgr.hxx @@ -44,11 +44,20 @@ const int MAX_MP_PROTOCOL_VERSION = 2; #include #include +#include "mpirc.hxx" +#include "cpdlc.hxx" + +const std::string MPIRC_SERVER_HOST_DEFAULT {"mpirc.flightgear.org"}; +const std::string MPIRC_SERVER_HOST_PROPERTY {"/network/mpirc/server-host"}; +const std::string MPIRC_SERVER_PORT_PROPERTY {"/network/mpirc/server-port"}; +const std::string MPIRC_NICK_PREFIX {"MP_IRC_"}; + struct FGExternalMotionData; class MPPropertyListener; struct T_MsgHdr; class FGAIMultiplayer; + class FGMultiplayMgr : public SGSubsystem { public: @@ -69,7 +78,7 @@ public: void SendTextMessage(const std::string &sMsgText); // receiver - FGAIMultiplayer* getMultiplayer(const std::string& callsign); + FGAIMultiplayer* getMultiplayer(const std::string& callsign); std::shared_ptr> popMessageHistory(); void pushMessageHistory(std::shared_ptr> message); @@ -77,8 +86,11 @@ public: // Remove motion information for all multiplayer aircraft, e.g. when // scrubbing during replay. void ClearMotion(); + CPDLCManager *getCPDLC() { return _cpdlc.get(); }; private: + std::unique_ptr _mpirc; + std::unique_ptr _cpdlc; friend class MPPropertyListener; void setPropertiesChanged() @@ -132,7 +144,7 @@ private: SGPropertyNode *pMultiPlayRange; SGPropertyNode *pMultiPlayTransmitPropertyBase; SGPropertyNode *pReplayState; - + typedef std::map PropertyDefinitionMap; PropertyDefinitionMap mPropertyDefinition;