Refactored CPDLP.
This commit is contained in:
parent
06bd0708b9
commit
527f58d353
7 changed files with 819 additions and 11 deletions
|
@ -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}")
|
||||
|
|
182
src/MultiPlayer/cpdlc.cxx
Normal file
182
src/MultiPlayer/cpdlc.cxx
Normal file
|
@ -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 <Main/fg_props.hxx>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
}
|
||||
}
|
82
src/MultiPlayer/cpdlc.hxx
Normal file
82
src/MultiPlayer/cpdlc.hxx
Normal file
|
@ -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 <deque>
|
||||
#include <string>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/io/raw_socket.hxx>
|
||||
#include <simgear/io/sg_socket.hxx>
|
||||
#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<struct IRCMessage> _incoming_messages;
|
||||
CPDLCStatus _status {CPDLC_OFFLINE};
|
||||
|
||||
SGPropertyNode *_pStatus {nullptr};
|
||||
SGPropertyNode *_pDataAuthority {nullptr};
|
||||
SGPropertyNode *_pMessage {nullptr};
|
||||
SGPropertyNode *_pNewMessage {nullptr};
|
||||
|
||||
void processMessage(struct IRCMessage entry);
|
||||
};
|
||||
|
||||
#endif
|
304
src/MultiPlayer/mpirc.cxx
Normal file
304
src/MultiPlayer/mpirc.cxx
Normal file
|
@ -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 <Main/fg_props.hxx>
|
||||
|
||||
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 <user>
|
||||
lines += " 0 * :"; //IRC <mode> <unused>
|
||||
lines += _nickname; //IRC <realname>
|
||||
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 <prefix>,
|
||||
<command> and list of parameters (<params>).
|
||||
|
||||
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;
|
||||
}
|
105
src/MultiPlayer/mpirc.hxx
Normal file
105
src/MultiPlayer/mpirc.hxx
Normal file
|
@ -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 <deque>
|
||||
#include <string>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/io/raw_socket.hxx>
|
||||
#include <simgear/io/sg_socket.hxx>
|
||||
|
||||
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<struct IRCMessage> _incoming_private_messages;
|
||||
|
||||
SGPropertyNode *_pReadyFlag {nullptr};
|
||||
SGPropertyNode *_pMessageCountIn {nullptr};
|
||||
SGPropertyNode *_pMessageCountOut {nullptr};
|
||||
SGPropertyNode *_pIRCReturnCode {nullptr};
|
||||
};
|
||||
|
||||
#endif
|
|
@ -33,16 +33,17 @@
|
|||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/misc/stdint.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/stdint.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/commands.hxx>
|
||||
#include <simgear/structure/event_mgr.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
#include <AIModel/AIManager.hxx>
|
||||
#include <AIModel/AIMultiplayer.hxx>
|
||||
|
@ -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<IRCConnection>(MPIRC_NICK_PREFIX + mCallsign, host, port);
|
||||
_mpirc->setupProperties("/network/mpirc/");
|
||||
|
||||
_cpdlc = std::make_unique<CPDLCManager>(_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()
|
||||
|
|
|
@ -44,11 +44,20 @@ const int MAX_MP_PROTOCOL_VERSION = 2;
|
|||
#include <simgear/io/raw_socket.hxx>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
#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<vector<char>> popMessageHistory();
|
||||
void pushMessageHistory(std::shared_ptr<vector<char>> 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<IRCConnection> _mpirc;
|
||||
std::unique_ptr<CPDLCManager> _cpdlc;
|
||||
friend class MPPropertyListener;
|
||||
|
||||
void setPropertiesChanged()
|
||||
|
@ -132,7 +144,7 @@ private:
|
|||
SGPropertyNode *pMultiPlayRange;
|
||||
SGPropertyNode *pMultiPlayTransmitPropertyBase;
|
||||
SGPropertyNode *pReplayState;
|
||||
|
||||
|
||||
typedef std::map<unsigned int, const struct IdPropertyList*> PropertyDefinitionMap;
|
||||
PropertyDefinitionMap mPropertyDefinition;
|
||||
|
||||
|
|
Loading…
Reference in a new issue