LCOV - code coverage report
Current view: top level - source/lobby - XmppClient.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 910 0.0 %
Date: 2022-06-14 00:41:00 Functions: 0 86 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "XmppClient.h"
      21             : #include "StanzaExtensions.h"
      22             : 
      23             : #include "i18n/L10n.h"
      24             : #include "lib/utf8.h"
      25             : #include "network/NetServer.h"
      26             : #include "network/NetClient.h"
      27             : #include "network/StunClient.h"
      28             : #include "ps/CLogger.h"
      29             : #include "ps/ConfigDB.h"
      30             : #include "ps/GUID.h"
      31             : #include "ps/Pyrogenesis.h"
      32             : #include "scriptinterface/ScriptInterface.h"
      33             : #include "scriptinterface/StructuredClone.h"
      34             : 
      35             : #include <iostream>
      36             : 
      37             : //debug
      38             : #if 1
      39             : #define DbgXMPP(x)
      40             : #else
      41             : #define DbgXMPP(x) std::cout << x << std::endl;
      42             : 
      43             : static std::string tag_xml(const glooxwrapper::IQ& iq)
      44             : {
      45             :     std::string ret;
      46             :     glooxwrapper::Tag* tag = iq.tag();
      47             :     ret = tag->xml().to_string();
      48             :     glooxwrapper::Tag::free(tag);
      49             :     return ret;
      50             : }
      51             : #endif
      52             : 
      53           0 : static std::string tag_name(const glooxwrapper::IQ& iq)
      54             : {
      55           0 :     std::string ret;
      56           0 :     glooxwrapper::Tag* tag = iq.tag();
      57           0 :     ret = tag->name().to_string();
      58           0 :     glooxwrapper::Tag::free(tag);
      59           0 :     return ret;
      60             : }
      61             : 
      62           0 : IXmppClient* IXmppClient::create(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt)
      63             : {
      64           0 :     return new XmppClient(scriptInterface, sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt);
      65             : }
      66             : 
      67             : /**
      68             :  * Construct the XMPP client.
      69             :  *
      70             :  * @param scriptInterface - ScriptInterface to be used for storing GUI messages.
      71             :  * Can be left blank for non-visual applications.
      72             :  * @param sUsername Username to login with of register.
      73             :  * @param sPassword Password to login with or register.
      74             :  * @param sRoom MUC room to join.
      75             :  * @param sNick Nick to join with.
      76             :  * @param historyRequestSize Number of stanzas of room history to request.
      77             :  * @param regOpt If we are just registering or not.
      78             :  */
      79           0 : XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
      80             :     : m_ScriptInterface(scriptInterface),
      81             :       m_client(nullptr),
      82             :       m_mucRoom(nullptr),
      83             :       m_registration(nullptr),
      84             :       m_username(sUsername),
      85             :       m_password(sPassword),
      86             :       m_room(sRoom),
      87             :       m_nick(sNick),
      88             :       m_initialLoadComplete(false),
      89             :       m_isConnected(false),
      90             :       m_sessionManager(nullptr),
      91             :       m_certStatus(gloox::CertStatus::CertOk),
      92             :       m_PlayerMapUpdate(false),
      93             :       m_connectionDataJid(),
      94           0 :       m_connectionDataIqId()
      95             : {
      96           0 :     if (m_ScriptInterface)
      97           0 :         JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), XmppClient::Trace, this);
      98             : 
      99             :     // Read lobby configuration from default.cfg
     100           0 :     std::string sXpartamupp;
     101           0 :     std::string sEchelon;
     102           0 :     CFG_GET_VAL("lobby.server", m_server);
     103           0 :     CFG_GET_VAL("lobby.xpartamupp", sXpartamupp);
     104           0 :     CFG_GET_VAL("lobby.echelon", sEchelon);
     105             : 
     106           0 :     m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC";
     107           0 :     m_echelonId = sEchelon + "@" + m_server + "/CC";
     108             :     // Generate a unique, unpredictable resource to allow multiple 0 A.D. instances to connect to the lobby.
     109           0 :     glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad-" + ps_generate_guid());
     110           0 :     glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick);
     111             : 
     112             :     // If we are connecting, use the full jid and a password
     113             :     // If we are registering, only use the server name
     114           0 :     if (!regOpt)
     115           0 :         m_client = new glooxwrapper::Client(clientJid, sPassword);
     116             :     else
     117           0 :         m_client = new glooxwrapper::Client(m_server);
     118             : 
     119             :     // Optionally join without a TLS certificate, so a local server can be tested  quickly.
     120             :     // Security risks from malicious JS mods can be mitigated if this option and also the hostname and login are shielded from JS access.
     121           0 :     bool tls = true;
     122           0 :     CFG_GET_VAL("lobby.tls", tls);
     123           0 :     m_client->setTls(tls ? gloox::TLSRequired : gloox::TLSDisabled);
     124             : 
     125             :     // Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
     126             :     // if the server doesn't list any supported SASL mechanism or the response
     127             :     // has been modified to exclude those.
     128           0 :     const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
     129           0 :     m_client->setSASLMechanisms(mechs);
     130             : 
     131           0 :     m_client->registerConnectionListener(this);
     132           0 :     m_client->setPresence(gloox::Presence::Available, -1);
     133           0 :     m_client->disco()->setVersion("Pyrogenesis", engine_version);
     134           0 :     m_client->disco()->setIdentity("client", "bot");
     135           0 :     m_client->setCompression(false);
     136             : 
     137           0 :     m_client->registerStanzaExtension(new GameListQuery());
     138           0 :     m_client->registerIqHandler(this, EXTGAMELISTQUERY);
     139             : 
     140           0 :     m_client->registerStanzaExtension(new BoardListQuery());
     141           0 :     m_client->registerIqHandler(this, EXTBOARDLISTQUERY);
     142             : 
     143           0 :     m_client->registerStanzaExtension(new ProfileQuery());
     144           0 :     m_client->registerIqHandler(this, EXTPROFILEQUERY);
     145             : 
     146           0 :     m_client->registerStanzaExtension(new LobbyAuth());
     147           0 :     m_client->registerIqHandler(this, EXTLOBBYAUTH);
     148             : 
     149           0 :     m_client->registerStanzaExtension(new ConnectionData());
     150           0 :     m_client->registerIqHandler(this, EXTCONNECTIONDATA);
     151             : 
     152           0 :     m_client->registerMessageHandler(this);
     153             : 
     154             :     // Uncomment to see the raw stanzas
     155             :     //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
     156             : 
     157           0 :     if (!regOpt)
     158             :     {
     159             :         // Create a Multi User Chat Room
     160           0 :         m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
     161             :         // Get room history.
     162           0 :         m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
     163             :     }
     164             :     else
     165             :     {
     166             :         // Registration
     167           0 :         m_registration = new glooxwrapper::Registration(m_client);
     168           0 :         m_registration->registerRegistrationHandler(this);
     169             :     }
     170             : 
     171           0 :     m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
     172             :     // Register plugins to allow gloox parse them in incoming sessions
     173           0 :     m_sessionManager->registerPlugins();
     174           0 : }
     175             : 
     176             : /**
     177             :  * Destroy the xmpp client
     178             :  */
     179           0 : XmppClient::~XmppClient()
     180             : {
     181           0 :     DbgXMPP("XmppClient destroyed");
     182           0 :     delete m_registration;
     183           0 :     delete m_mucRoom;
     184           0 :     delete m_sessionManager;
     185             : 
     186             :     // Workaround for memory leak in gloox 1.0/1.0.1
     187           0 :     m_client->removePresenceExtension(gloox::ExtCaps);
     188             : 
     189           0 :     delete m_client;
     190             : 
     191           0 :     for (const glooxwrapper::Tag* const& t : m_GameList)
     192           0 :         glooxwrapper::Tag::free(t);
     193           0 :     for (const glooxwrapper::Tag* const& t : m_BoardList)
     194           0 :         glooxwrapper::Tag::free(t);
     195           0 :     for (const glooxwrapper::Tag* const& t : m_Profile)
     196           0 :         glooxwrapper::Tag::free(t);
     197             : 
     198           0 :     if (m_ScriptInterface)
     199           0 :         JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), XmppClient::Trace, this);
     200           0 : }
     201           0 : 
     202             : void XmppClient::TraceMember(JSTracer* trc)
     203             : {
     204             :     for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
     205             :         JS::TraceEdge(trc, &guiMessage, "m_GuiMessageQueue");
     206             : 
     207             :     for (JS::Heap<JS::Value>& guiMessage : m_HistoricGuiMessages)
     208             :         JS::TraceEdge(trc, &guiMessage, "m_HistoricGuiMessages");
     209             : }
     210             : 
     211             : /// Network
     212             : void XmppClient::connect()
     213             : {
     214             :     m_initialLoadComplete = false;
     215             :     m_client->connect(false);
     216             : }
     217             : 
     218             : void XmppClient::disconnect()
     219             : {
     220             :     m_client->disconnect();
     221             : }
     222           0 : 
     223           0 : bool XmppClient::isConnected()
     224             : {
     225           0 :     return m_isConnected;
     226           0 : }
     227           0 : 
     228           0 : void XmppClient::recv()
     229             : {
     230             :     m_client->recv(1);
     231           0 : }
     232             : 
     233           0 : /**
     234             :  * Log (debug) Handler
     235           0 :  */
     236           0 : void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
     237           0 : {
     238           0 :     std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
     239           0 : }
     240           0 : 
     241             : /*****************************************************
     242           0 :  * Connection handlers                               *
     243           0 :  *****************************************************/
     244           0 : 
     245             : /**
     246           0 :  * Handle connection
     247             :  */
     248           0 : void XmppClient::onConnect()
     249           0 : {
     250             :     if (m_mucRoom)
     251           0 :     {
     252           0 :         m_isConnected = true;
     253           0 :         CreateGUIMessage("system", "connected", std::time(nullptr));
     254             :         m_mucRoom->join();
     255             :     }
     256           0 : 
     257             :     if (m_registration)
     258           0 :         m_registration->fetchRegistrationFields();
     259           0 : }
     260           0 : 
     261             : /**
     262           0 :  * Handle disconnection
     263             :  */
     264           0 : void XmppClient::onDisconnect(gloox::ConnectionError error)
     265           0 : {
     266             :     // Make sure we properly leave the room so that
     267           0 :     // everything works if we decide to come back later
     268             :     if (m_mucRoom)
     269           0 :         m_mucRoom->leave();
     270             : 
     271             :     // Clear game, board and player lists.
     272           0 :     for (const glooxwrapper::Tag* const& t : m_GameList)
     273             :         glooxwrapper::Tag::free(t);
     274           0 :     for (const glooxwrapper::Tag* const& t : m_BoardList)
     275           0 :         glooxwrapper::Tag::free(t);
     276             :     for (const glooxwrapper::Tag* const& t : m_Profile)
     277             :         glooxwrapper::Tag::free(t);
     278             : 
     279             :     m_BoardList.clear();
     280           0 :     m_GameList.clear();
     281             :     m_PlayerMap.clear();
     282           0 :     m_PlayerMapUpdate = true;
     283           0 :     m_Profile.clear();
     284             :     m_HistoricGuiMessages.clear();
     285             :     m_isConnected = false;
     286             :     m_initialLoadComplete = false;
     287             : 
     288             :     CreateGUIMessage(
     289             :         "system",
     290             :         "disconnected",
     291             :         std::time(nullptr),
     292           0 :         "reason", error,
     293             :         "certificate_status", m_certStatus);
     294           0 : }
     295             : 
     296           0 : /**
     297           0 :  * Handle TLS connection.
     298           0 :  */
     299             : bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
     300             : {
     301           0 :     DbgXMPP("onTLSConnect");
     302           0 :     DbgXMPP(
     303           0 :         "status: " << info.status <<
     304             :         "\nissuer: " << info.issuer <<
     305             :         "\npeer: " << info.server <<
     306             :         "\nprotocol: " << info.protocol <<
     307             :         "\nmac: " << info.mac <<
     308           0 :         "\ncipher: " << info.cipher <<
     309             :         "\ncompression: " << info.compression );
     310             : 
     311             :     m_certStatus = static_cast<gloox::CertStatus>(info.status);
     312           0 : 
     313           0 :     // Optionally accept invalid certificates, see require_tls option.
     314             :     bool verify_certificate = true;
     315             :     CFG_GET_VAL("lobby.verify_certificate", verify_certificate);
     316           0 : 
     317           0 :     return info.status == gloox::CertOk || !verify_certificate;
     318           0 : }
     319           0 : 
     320           0 : /**
     321           0 :  * Handle MUC room errors
     322             :  */
     323           0 : void XmppClient::handleMUCError(glooxwrapper::MUCRoom& UNUSED(room), gloox::StanzaError err)
     324           0 : {
     325           0 :     DbgXMPP("MUC Error " << ": " << StanzaErrorToString(err));
     326           0 :     CreateGUIMessage("system", "error", std::time(nullptr), "text", err);
     327           0 : }
     328           0 : 
     329           0 : /*****************************************************
     330           0 :  * Requests to server                                *
     331             :  *****************************************************/
     332           0 : 
     333             : /**
     334             :  * Request the leaderboard data from the server.
     335             :  */
     336             : void XmppClient::SendIqGetBoardList()
     337           0 : {
     338           0 :     glooxwrapper::JID echelonJid(m_echelonId);
     339             : 
     340             :     // Send IQ
     341             :     BoardListQuery* b = new BoardListQuery();
     342             :     b->m_Command = "getleaderboard";
     343           0 :     glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
     344             :     iq.addExtension(b);
     345           0 :     DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
     346             :     m_client->send(iq);
     347             : }
     348             : 
     349             : /**
     350             :  * Request the profile data from the server.
     351             :  */
     352             : void XmppClient::SendIqGetProfile(const std::string& player)
     353           0 : {
     354             :     glooxwrapper::JID echelonJid(m_echelonId);
     355           0 : 
     356             :     // Send IQ
     357             :     ProfileQuery* b = new ProfileQuery();
     358           0 :     b->m_Command = player;
     359           0 :     glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
     360             :     iq.addExtension(b);
     361           0 :     DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
     362             :     m_client->send(iq);
     363             : }
     364             : 
     365             : /**
     366             :  * Request the Connection data (ip, port...) from the server.
     367           0 :  */
     368             : void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password, const std::string& clientSalt, bool localIP)
     369           0 : {
     370           0 :     glooxwrapper::JID targetJID(jid);
     371           0 : 
     372             :     ConnectionData* connectionData = new ConnectionData();
     373             :     connectionData->m_Password = password;
     374             :     connectionData->m_ClientSalt = clientSalt;
     375             :     connectionData->m_IsLocalIP = localIP ? "1" : "0";
     376             :     glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID());
     377             :     iq.addExtension(connectionData);
     378             :     m_connectionDataJid = iq.from().full();
     379             :     m_connectionDataIqId = iq.id().to_string();
     380           0 :     DbgXMPP("SendIqGetConnectionData [" << tag_xml(iq) << "]");
     381             :     m_client->send(iq);
     382           0 : }
     383             : 
     384             : /**
     385           0 :  * Send game report containing numerous game properties to the server.
     386           0 :  *
     387           0 :  * @param data A JS array of game statistics
     388           0 :  */
     389           0 : void XmppClient::SendIqGameReport(const ScriptRequest& rq, JS::HandleValue data)
     390           0 : {
     391           0 :     glooxwrapper::JID echelonJid(m_echelonId);
     392             : 
     393             :     // Setup some base stanza attributes
     394             :     GameReport* game = new GameReport();
     395             :     glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
     396           0 : 
     397             :     // Iterate through all the properties reported and add them to the stanza.
     398           0 :     std::vector<std::string> properties;
     399             :     Script::EnumeratePropertyNames(rq, data, true, properties);
     400             :     for (const std::string& p : properties)
     401           0 :     {
     402           0 :         std::wstring value;
     403           0 :         Script::GetProperty(rq, data, p.c_str(), value);
     404           0 :         report->addAttribute(p, utf8_from_wstring(value));
     405           0 :     }
     406           0 : 
     407           0 :     // Add stanza to IQ
     408             :     game->m_GameReport.emplace_back(report);
     409             : 
     410             :     // Send IQ
     411             :     glooxwrapper::IQ iq(gloox::IQ::Set, echelonJid, m_client->getID());
     412           0 :     iq.addExtension(game);
     413             :     DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
     414           0 :     m_client->send(iq);
     415             : };
     416           0 : 
     417           0 : /**
     418           0 :  * Send a request to register a game to the server.
     419           0 :  *
     420           0 :  * @param data A JS array of game attributes
     421           0 :  */
     422           0 : void XmppClient::SendIqRegisterGame(const ScriptRequest& rq, JS::HandleValue data)
     423           0 : {
     424           0 :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     425           0 : 
     426           0 :     // Setup some base stanza attributes
     427             :     std::unique_ptr<GameListQuery> g = std::make_unique<GameListQuery>();
     428             :     g->m_Command = "register";
     429             :     glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
     430             : 
     431             :     // Iterate through all the properties reported and add them to the stanza.
     432             :     std::vector<std::string> properties;
     433           0 :     Script::EnumeratePropertyNames(rq, data, true, properties);
     434             :     for (const std::string& p : properties)
     435           0 :     {
     436             :         std::string value;
     437             :         if (!Script::GetProperty(rq, data, p.c_str(), value))
     438           0 :         {
     439           0 :             LOGERROR("Could not parse attribute '%s' as string.", p);
     440             :             return;
     441             :         }
     442           0 :         game->addAttribute(p, value);
     443           0 :     }
     444           0 : 
     445             :     // Overwrite some attributes to make it slightly less trivial to do bad things,
     446           0 :     // and explicit some invariants.
     447           0 : 
     448           0 :     // The JID must point to ourself.
     449             :     game->addAttribute("hostJID", GetJID());
     450             : 
     451             :     // Push the stanza onto the IQ
     452           0 :     g->m_GameList.emplace_back(game);
     453             : 
     454             :     // Send IQ
     455           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
     456           0 :     iq.addExtension(g.release());
     457           0 :     DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
     458           0 :     m_client->send(iq);
     459           0 : }
     460             : 
     461             : /**
     462             :  * Send a request to unregister a game to the server.
     463             :  */
     464             : void XmppClient::SendIqUnregisterGame()
     465             : {
     466           0 :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     467             : 
     468           0 :     // Send IQ
     469             :     GameListQuery* g = new GameListQuery();
     470             :     g->m_Command = "unregister";
     471           0 :     g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game"));
     472           0 : 
     473           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
     474             :     iq.addExtension(g);
     475             :     DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
     476           0 :     m_client->send(iq);
     477           0 : }
     478           0 : 
     479             : /**
     480           0 :  * Send a request to change the state of a registered game on the server.
     481           0 :  *
     482             :  * A game can either be in the 'running' or 'waiting' state - the server
     483           0 :  * decides which - but we need to update the current players that are
     484           0 :  * in-game so the server can make the calculation.
     485             :  */
     486           0 : void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
     487             : {
     488             :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     489             : 
     490             :     // Send IQ
     491             :     GameListQuery* g = new GameListQuery();
     492             :     g->m_Command = "changestate";
     493           0 :     glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
     494             :     game->addAttribute("nbp", nbp);
     495             :     game->addAttribute("players", players);
     496           0 :     g->m_GameList.emplace_back(game);
     497             : 
     498             :     glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
     499           0 :     iq.addExtension(g);
     500           0 :     DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
     501           0 :     m_client->send(iq);
     502           0 : }
     503             : 
     504             : /*****************************************************
     505             :  * iq to clients                                     *
     506             :  *****************************************************/
     507             : 
     508           0 : /**
     509             :  * Send lobby authentication token.
     510           0 :  */
     511             : void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token)
     512             : {
     513           0 :     LobbyAuth* auth = new LobbyAuth();
     514           0 :     auth->m_Token = token;
     515           0 : 
     516             :     glooxwrapper::JID clientJid(to);
     517           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID());
     518           0 :     iq.addExtension(auth);
     519           0 :     DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]");
     520           0 :     m_client->send(iq);
     521           0 : }
     522             : 
     523             : /*****************************************************
     524             :  * Account registration                              *
     525             :  *****************************************************/
     526             : 
     527             : void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
     528             : {
     529             :     glooxwrapper::RegistrationFields vals;
     530           0 :     vals.username = m_username;
     531             :     vals.password = m_password;
     532           0 :     m_registration->createAccount(fields, vals);
     533             : }
     534             : 
     535           0 : void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
     536           0 : {
     537           0 :     if (result == gloox::RegistrationSuccess)
     538           0 :         CreateGUIMessage("system", "registered", std::time(nullptr));
     539           0 :     else
     540           0 :         CreateGUIMessage("system", "error", std::time(nullptr), "text", result);
     541             : 
     542           0 :     disconnect();
     543           0 : }
     544           0 : 
     545           0 : void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
     546           0 : {
     547             :     DbgXMPP("the account already exists");
     548             : }
     549             : 
     550             : void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
     551             : {
     552             :     DbgXMPP("dataForm received");
     553             : }
     554             : 
     555           0 : void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
     556             : {
     557           0 :     DbgXMPP("OOB registration requested");
     558           0 : }
     559             : 
     560           0 : /*****************************************************
     561           0 :  * Requests from GUI                                 *
     562           0 :  *****************************************************/
     563           0 : 
     564           0 : /**
     565           0 :  * Handle requests from the GUI for the list of players.
     566             :  *
     567             :  * @return A JS array containing all known players and their presences
     568             :  */
     569             : JS::Value XmppClient::GUIGetPlayerList(const ScriptRequest& rq)
     570             : {
     571           0 :     JS::RootedValue ret(rq.cx);
     572             :     Script::CreateArray(rq, &ret);
     573           0 :     int j = 0;
     574           0 : 
     575           0 :     for (const std::pair<const glooxwrapper::string, SPlayer>& p : m_PlayerMap)
     576           0 :     {
     577           0 :         JS::RootedValue player(rq.cx);
     578             : 
     579           0 :         Script::CreateObject(
     580             :             rq,
     581           0 :             &player,
     582           0 :             "name", p.first,
     583             :             "presence", p.second.m_Presence,
     584           0 :             "rating", p.second.m_Rating,
     585             :             "role", p.second.m_Role);
     586           0 : 
     587           0 :         Script::SetPropertyInt(rq, ret, j++, player);
     588             :     }
     589           0 :     return ret;
     590             : }
     591           0 : 
     592           0 : /**
     593             :  * Handle requests from the GUI for the list of all active games.
     594           0 :  *
     595             :  * @return A JS array containing all known games
     596           0 :  */
     597           0 : JS::Value XmppClient::GUIGetGameList(const ScriptRequest& rq)
     598             : {
     599           0 :     JS::RootedValue ret(rq.cx);
     600             :     Script::CreateArray(rq, &ret);
     601           0 :     int j = 0;
     602           0 : 
     603             :     const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword",
     604             :         "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
     605             :         "victoryConditions", "startTime", "mods" };
     606             : 
     607             :     for(const glooxwrapper::Tag* const& t : m_GameList)
     608             :     {
     609             :         JS::RootedValue game(rq.cx);
     610             :         Script::CreateObject(rq, &game);
     611             : 
     612             :         for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
     613           0 :             Script::SetProperty(rq, game, stats[i], t->findAttribute(stats[i]));
     614             : 
     615           0 :         Script::SetPropertyInt(rq, ret, j++, game);
     616           0 :     }
     617           0 :     return ret;
     618             : }
     619           0 : 
     620             : /**
     621           0 :  * Handle requests from the GUI for leaderboard data.
     622             :  *
     623           0 :  * @return A JS array containing all known leaderboard data
     624             :  */
     625             : JS::Value XmppClient::GUIGetBoardList(const ScriptRequest& rq)
     626           0 : {
     627           0 :     JS::RootedValue ret(rq.cx);
     628           0 :     Script::CreateArray(rq, &ret);
     629           0 :     int j = 0;
     630             : 
     631           0 :     const char* attributes[] = { "name", "rank", "rating" };
     632             : 
     633           0 :     for(const glooxwrapper::Tag* const& t : m_BoardList)
     634             :     {
     635             :         JS::RootedValue board(rq.cx);
     636             :         Script::CreateObject(rq, &board);
     637             : 
     638             :         for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
     639             :             Script::SetProperty(rq, board, attributes[i], t->findAttribute(attributes[i]));
     640             : 
     641           0 :         Script::SetPropertyInt(rq, ret, j++, board);
     642             :     }
     643           0 :     return ret;
     644           0 : }
     645           0 : 
     646             : /**
     647           0 :  * Handle requests from the GUI for profile data.
     648             :  *
     649             :  * @return A JS array containing the specific user's profile data
     650             :  */
     651           0 : JS::Value XmppClient::GUIGetProfile(const ScriptRequest& rq)
     652             : {
     653           0 :     JS::RootedValue ret(rq.cx);
     654           0 :     Script::CreateArray(rq, &ret);
     655             :     int j = 0;
     656           0 : 
     657           0 :     const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
     658             : 
     659           0 :     for (const glooxwrapper::Tag* const& t : m_Profile)
     660             :     {
     661           0 :         JS::RootedValue profile(rq.cx);
     662             :         Script::CreateObject(rq, &profile);
     663             : 
     664             :         for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
     665             :             Script::SetProperty(rq, profile, stats[i], t->findAttribute(stats[i]));
     666             : 
     667             :         Script::SetPropertyInt(rq, ret, j++, profile);
     668             :     }
     669           0 :     return ret;
     670             : }
     671           0 : 
     672           0 : /*****************************************************
     673           0 :  * Message interfaces                                *
     674             :  *****************************************************/
     675           0 : 
     676             : void SetGUIMessageProperty(const ScriptRequest& UNUSED(rq), JS::HandleObject UNUSED(messageObj))
     677           0 : {
     678             : }
     679           0 : 
     680           0 : template<typename T, typename... Args>
     681             : void SetGUIMessageProperty(const ScriptRequest& rq, JS::HandleObject messageObj, const std::string& propertyName, const T& propertyValue, Args const&... args)
     682           0 : {
     683           0 :     JS::RootedValue scriptPropertyValue(rq.cx);
     684             :     Script::ToJSVal(rq, &scriptPropertyValue, propertyValue);
     685           0 :     JS_DefineProperty(rq.cx, messageObj, propertyName.c_str(), scriptPropertyValue, JSPROP_ENUMERATE);
     686             :     SetGUIMessageProperty(rq, messageObj, args...);
     687           0 : }
     688             : 
     689             : template<typename... Args>
     690             : void XmppClient::CreateGUIMessage(
     691             :     const std::string& type,
     692             :     const std::string& level,
     693             :     const std::time_t time,
     694             :     Args const&... args)
     695           0 : {
     696             :     if (!m_ScriptInterface)
     697           0 :         return;
     698           0 :     ScriptRequest rq(m_ScriptInterface);
     699           0 :     JS::RootedValue message(rq.cx);
     700             :     Script::CreateObject(
     701           0 :         rq,
     702             :         &message,
     703           0 :         "type", type,
     704             :         "level", level,
     705           0 :         "historic", false,
     706           0 :         "time", static_cast<double>(time));
     707             : 
     708           0 :     JS::RootedObject messageObj(rq.cx, message.toObjectOrNull());
     709           0 :     SetGUIMessageProperty(rq, messageObj, args...);
     710             :     Script::FreezeObject(rq, message, true);
     711           0 :     m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
     712             : }
     713           0 : 
     714             : bool XmppClient::GuiPollHasPlayerListUpdate()
     715             : {
     716             :     // The initial playerlist will be received in multiple messages
     717             :     // Only inform the GUI after all of these playerlist fragments were received.
     718             :     if (!m_initialLoadComplete)
     719             :         return false;
     720           0 : 
     721             :     bool hasUpdate = m_PlayerMapUpdate;
     722           0 :     m_PlayerMapUpdate = false;
     723             :     return hasUpdate;
     724             : }
     725           0 : 
     726             : JS::Value XmppClient::GuiPollNewMessages(const ScriptInterface& guiInterface)
     727           0 : {
     728           0 :     if ((m_isConnected && !m_initialLoadComplete) || m_GuiMessageQueue.empty())
     729           0 :         return JS::UndefinedValue();
     730           0 : 
     731           0 :     ScriptRequest rq(m_ScriptInterface);
     732           0 : 
     733             :     // Optimize for batch message processing that is more
     734           0 :     // performance demanding than processing a lone message.
     735           0 :     JS::RootedValue messages(rq.cx);
     736           0 :     Script::CreateArray(rq, &messages);
     737           0 : 
     738           0 :     int j = 0;
     739           0 : 
     740             :     for (const JS::Heap<JS::Value>& message : m_GuiMessageQueue)
     741           0 :     {
     742           0 :         Script::SetPropertyInt(rq, messages, j++, message);
     743           0 : 
     744           0 :         // Store historic chat messages.
     745           0 :         // Only store relevant messages to minimize memory footprint.
     746           0 :         JS::RootedValue rootedMessage(rq.cx, message);
     747             :         std::string type;
     748           0 :         Script::GetProperty(rq, rootedMessage, "type", type);
     749           0 :         if (type != "chat")
     750           0 :             continue;
     751           0 : 
     752           0 :         std::string level;
     753           0 :         Script::GetProperty(rq, rootedMessage, "level", level);
     754             :         if (level != "room-message" && level != "private-message")
     755           0 :             continue;
     756           0 : 
     757           0 :         JS::RootedValue historicMessage(rq.cx, Script::DeepCopy(rq, rootedMessage));
     758           0 :         if (true)
     759           0 :         {
     760           0 :             Script::SetProperty(rq, historicMessage, "historic", true);
     761             :             Script::FreezeObject(rq, historicMessage, true);
     762           0 :             m_HistoricGuiMessages.push_back(JS::Heap<JS::Value>(historicMessage));
     763           0 :         }
     764           0 :         else
     765           0 :             LOGERROR("Could not clone historic lobby GUI message!");
     766           0 :     }
     767           0 :     m_GuiMessageQueue.clear();
     768             : 
     769           0 :     // Copy the messages over to the caller script interface.
     770           0 :     return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
     771           0 : }
     772           0 : 
     773           0 : JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& guiInterface)
     774           0 : {
     775             :     if (m_HistoricGuiMessages.empty())
     776           0 :         return JS::UndefinedValue();
     777           0 : 
     778           0 :     ScriptRequest rq(m_ScriptInterface);
     779           0 : 
     780           0 :     JS::RootedValue messages(rq.cx);
     781           0 :     Script::CreateArray(rq, &messages);
     782             : 
     783           0 :     int j = 0;
     784           0 :     for (const JS::Heap<JS::Value>& message : m_HistoricGuiMessages)
     785           0 :         Script::SetPropertyInt(rq, messages, j++, message);
     786           0 : 
     787           0 :     // Copy the messages over to the caller script interface.
     788           0 :     return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
     789             : }
     790           0 : 
     791           0 : /**
     792           0 :  * Send a standard MUC textual message.
     793           0 :  */
     794           0 : void XmppClient::SendMUCMessage(const std::string& message)
     795           0 : {
     796             :     m_mucRoom->send(message);
     797           0 : }
     798           0 : 
     799           0 : /**
     800           0 :  * Handle a room message.
     801           0 :  */
     802           0 : void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::Message& msg, bool priv)
     803             : {
     804           0 :     DbgXMPP(msg.from().resource() << " said " << msg.body());
     805           0 : 
     806           0 :     CreateGUIMessage(
     807           0 :         "chat",
     808           0 :         priv ? "private-message" : "room-message",
     809           0 :         ComputeTimestamp(msg),
     810             :         "from", msg.from().resource(),
     811           0 :         "text", msg.body());
     812           0 : }
     813           0 : 
     814           0 : /**
     815           0 :  * Handle a private message.
     816           0 :  */
     817             : void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession*)
     818           0 : {
     819           0 :     DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
     820           0 :       << ", message " << msg.body() << ", thread id " << msg.thread());
     821           0 : 
     822           0 :     CreateGUIMessage(
     823             :         "chat",
     824             :         "private-message",
     825           0 :         ComputeTimestamp(msg),
     826             :         "from", msg.from().resource(),
     827             :         "text", msg.body());
     828             : }
     829             : 
     830             : /**
     831           0 :  * Handle portions of messages containing custom stanza extensions.
     832           0 :  */
     833           0 : bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
     834           0 : {
     835           0 :     DbgXMPP("handleIq [" << tag_xml(iq) << "]");
     836             : 
     837             :     if (iq.subtype() == gloox::IQ::Result)
     838             :     {
     839             :         const GameListQuery* gq = iq.findExtension<GameListQuery>(EXTGAMELISTQUERY);
     840           0 :         const BoardListQuery* bq = iq.findExtension<BoardListQuery>(EXTBOARDLISTQUERY);
     841             :         const ProfileQuery* pq = iq.findExtension<ProfileQuery>(EXTPROFILEQUERY);
     842             :         const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
     843           0 :         if (cd)
     844           0 :         {
     845           0 :             if (g_NetServer || !g_NetClient)
     846           0 :                 return true;
     847             : 
     848           0 :             if (!m_connectionDataJid.empty() && m_connectionDataJid.compare(iq.from().full()) != 0) {
     849             :                 LOGMESSAGE("XmppClient: Received connection data from invalid host: %s", iq.from().username());
     850             :                 return true;
     851             :             }
     852             : 
     853             :             if (!m_connectionDataIqId.empty() && m_connectionDataIqId.compare(iq.id().to_string()) != 0) {
     854           0 :                 LOGMESSAGE("XmppClient: Received connection data with invalid id");
     855           0 :                 return true;
     856           0 :             }
     857           0 : 
     858           0 :             if (!cd->m_Error.empty())
     859             :             {
     860             :                 g_NetClient->HandleGetServerDataFailed(cd->m_Error.c_str());
     861             :                 return true;
     862             :             }
     863           0 : 
     864             :             g_NetClient->SetupServerData(cd->m_Ip.to_string(), stoi(cd->m_Port.to_string()), !cd->m_UseSTUN.empty());
     865             :             g_NetClient->TryToConnect(iq.from().full(), !cd->m_IsLocalIP.empty());
     866           0 :         }
     867           0 :         if (gq)
     868           0 :         {
     869           0 :             if (iq.from().full() == m_xpartamuppId && gq->m_Command == "register" && g_NetServer && !g_NetServer->GetUseSTUN())
     870             :             {
     871           0 :                 if (gq->m_GameList.empty())
     872             :                 {
     873             :                     LOGWARNING("XmppClient: Received empty game list in response to Game Register");
     874             :                     return true;
     875             :                 }
     876             :                 std::string publicIP = gq->m_GameList.front()->findAttribute("ip").to_string();
     877           0 :                 if (publicIP.empty())
     878           0 :                 {
     879           0 :                     LOGWARNING("XmppClient: Received game with no IP in response to Game Register");
     880           0 :                     return true;
     881           0 :                 }
     882             :                 g_NetServer->SetConnectionData(publicIP, g_NetServer->GetPublicPort());
     883             :                 return true;
     884             :             }
     885             : 
     886           0 :             for (const glooxwrapper::Tag* const& t : m_GameList)
     887             :                 glooxwrapper::Tag::free(t);
     888             :             m_GameList.clear();
     889           0 : 
     890           0 :             for (const glooxwrapper::Tag* const& t : gq->m_GameList)
     891           0 :                 m_GameList.emplace_back(t->clone());
     892           0 : 
     893             :             CreateGUIMessage("game", "gamelist", std::time(nullptr));
     894           0 :         }
     895             :         if (bq)
     896             :         {
     897             :             if (bq->m_Command == "boardlist")
     898             :             {
     899             :                 for (const glooxwrapper::Tag* const& t : m_BoardList)
     900           0 :                     glooxwrapper::Tag::free(t);
     901           0 :                 m_BoardList.clear();
     902           0 : 
     903           0 :                 for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
     904           0 :                     m_BoardList.emplace_back(t->clone());
     905             : 
     906             :                 CreateGUIMessage("game", "leaderboard", std::time(nullptr));
     907             :             }
     908             :             else if (bq->m_Command == "ratinglist")
     909           0 :             {
     910             :                 for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
     911             :                 {
     912           0 :                     const PlayerMap::iterator it = m_PlayerMap.find(t->findAttribute("name"));
     913           0 :                     if (it != m_PlayerMap.end())
     914           0 :                     {
     915           0 :                         it->second.m_Rating = t->findAttribute("rating");
     916             :                         m_PlayerMapUpdate = true;
     917           0 :                     }
     918             :                 }
     919             :                 CreateGUIMessage("game", "ratinglist", std::time(nullptr));
     920             :             }
     921             :         }
     922             :         if (pq)
     923           0 :         {
     924           0 :             for (const glooxwrapper::Tag* const& t : m_Profile)
     925           0 :                 glooxwrapper::Tag::free(t);
     926           0 :             m_Profile.clear();
     927           0 : 
     928             :             for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile)
     929             :                 m_Profile.emplace_back(t->clone());
     930             : 
     931             :             CreateGUIMessage("game", "profile", std::time(nullptr));
     932           0 :         }
     933             :     }
     934             :     else if (iq.subtype() == gloox::IQ::Set)
     935           0 :     {
     936           0 :         const LobbyAuth* lobbyAuth = iq.findExtension<LobbyAuth>(EXTLOBBYAUTH);
     937           0 :         if (lobbyAuth)
     938           0 :         {
     939             :             LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username());
     940           0 : 
     941             :             glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     942             :             m_client->send(response);
     943             : 
     944             :             if (g_NetServer)
     945             :                 g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string());
     946           0 :             else
     947           0 :                 LOGMESSAGE("Received lobby authentication request, but not hosting currently!");
     948           0 :         }
     949           0 :     }
     950           0 :     else if (iq.subtype() == gloox::IQ::Get)
     951             :     {
     952             :         const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
     953             :         if (cd)
     954             :         {
     955           0 :             LOGMESSAGE("XmppClient: Received request for connection data from %s", iq.from().username());
     956             :             if (!g_NetServer)
     957             :             {
     958           0 :                 glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     959           0 :                 ConnectionData* connectionData = new ConnectionData();
     960           0 :                 connectionData->m_Error = "not_server";
     961           0 : 
     962             :                 response.addExtension(connectionData);
     963           0 : 
     964             :                 m_client->send(response);
     965             :                 return true;
     966             :             }
     967             :             if (g_NetServer->IsBanned(iq.from().username()))
     968             :             {
     969           0 :                 glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     970           0 :                 ConnectionData* connectionData = new ConnectionData();
     971           0 :                 connectionData->m_Error = "banned";
     972           0 : 
     973           0 :                 response.addExtension(connectionData);
     974             : 
     975             :                 m_client->send(response);
     976             :                 return true;
     977             :             }
     978           0 :             if (!g_NetServer->CheckPasswordAndIncrement(iq.from().username(), cd->m_Password.to_string(), cd->m_ClientSalt.to_string()))
     979             :             {
     980             :                 glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     981           0 :                 ConnectionData* connectionData = new ConnectionData();
     982           0 :                 connectionData->m_Error = "invalid_password";
     983           0 : 
     984           0 :                 response.addExtension(connectionData);
     985             : 
     986           0 :                 m_client->send(response);
     987             :                 return true;
     988             :             }
     989             : 
     990             :             glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     991             :             ConnectionData* connectionData = new ConnectionData();
     992           0 : 
     993           0 :             if (cd->m_IsLocalIP.to_string() == "0")
     994           0 :             {
     995           0 :                 connectionData->m_Ip = g_NetServer->GetPublicIp();
     996           0 :                 connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort());
     997             :                 connectionData->m_UseSTUN = g_NetServer->GetUseSTUN() ? "true" : "";
     998             :                 connectionData->m_IsLocalIP = "";
     999             :             }
    1000             :             else
    1001           0 :             {
    1002             :                 CStr ip;
    1003             :                 if (StunClient::FindLocalIP(ip))
    1004           0 :                 {
    1005           0 :                     connectionData->m_Ip = ip;
    1006           0 :                     connectionData->m_Port = std::to_string(g_NetServer->GetLocalPort());
    1007           0 :                     connectionData->m_UseSTUN = "";
    1008             :                     connectionData->m_IsLocalIP = "true";
    1009           0 :                 }
    1010             :                 else
    1011             :                     connectionData->m_Error = "local_ip_failed";
    1012             :             }
    1013             : 
    1014             :             response.addExtension(connectionData);
    1015           0 : 
    1016           0 :             m_client->send(response);
    1017           0 :         }
    1018           0 : 
    1019           0 :     }
    1020             :     else if (iq.subtype() == gloox::IQ::Error)
    1021             :         CreateGUIMessage("system", "error", std::time(nullptr), "text", iq.error_error());
    1022             :     else
    1023             :     {
    1024           0 :         CreateGUIMessage("system", "error", std::time(nullptr), "text", wstring_from_utf8(g_L10n.Translate("unknown subtype (see logs)")));
    1025             :         LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str());
    1026             :     }
    1027           0 : 
    1028           0 :     return true;
    1029           0 : }
    1030           0 : 
    1031             : /**
    1032           0 :  * Update local data when a user changes presence.
    1033             :  */
    1034             : void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
    1035             : {
    1036             :     const glooxwrapper::string& nick = participant.nick->resource();
    1037             : 
    1038           0 :     if (presence.presence() == gloox::Presence::Unavailable)
    1039           0 :     {
    1040           0 :         if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
    1041           0 :         {
    1042           0 :             // we have a nick change
    1043             :             if (m_PlayerMap.find(participant.newNick) == m_PlayerMap.end())
    1044             :                 m_PlayerMap.emplace(
    1045             :                     std::piecewise_construct,
    1046             :                     std::forward_as_tuple(participant.newNick),
    1047           0 :                     std::forward_as_tuple(presence.presence(), participant.role, std::move(m_PlayerMap.at(nick).m_Rating)));
    1048             :             else
    1049             :                 LOGERROR("Nickname changed to an existing nick!");
    1050           0 : 
    1051           0 :             DbgXMPP(nick << " is now known as " << participant.newNick);
    1052           0 :             CreateGUIMessage(
    1053           0 :                 "chat",
    1054             :                 "nick",
    1055           0 :                 std::time(nullptr),
    1056             :                 "oldnick", nick,
    1057             :                 "newnick", participant.newNick);
    1058             :         }
    1059             :         else if (participant.flags & gloox::UserKicked)
    1060             :         {
    1061           0 :             DbgXMPP(nick << " was kicked. Reason: " << participant.reason);
    1062           0 :             CreateGUIMessage(
    1063           0 :                 "chat",
    1064           0 :                 "kicked",
    1065           0 :                 std::time(nullptr),
    1066             :                 "nick", nick,
    1067             :                 "reason", participant.reason);
    1068             :         }
    1069             :         else if (participant.flags & gloox::UserBanned)
    1070           0 :         {
    1071             :             DbgXMPP(nick << " was banned. Reason: " << participant.reason);
    1072             :             CreateGUIMessage(
    1073           0 :                 "chat",
    1074           0 :                 "banned",
    1075           0 :                 std::time(nullptr),
    1076           0 :                 "nick", nick,
    1077             :                 "reason", participant.reason);
    1078           0 :         }
    1079             :         else
    1080             :         {
    1081             :             DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
    1082             :             CreateGUIMessage(
    1083             :                 "chat",
    1084           0 :                 "leave",
    1085           0 :                 std::time(nullptr),
    1086           0 :                 "nick", nick);
    1087           0 :         }
    1088           0 :         m_PlayerMap.erase(nick);
    1089             :     }
    1090             :     else
    1091             :     {
    1092             :         const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1093           0 : 
    1094             :         /* During the initialization process, we receive join messages for everyone
    1095             :          * currently in the room. We don't want to display these, so we filter them
    1096           0 :          * out. We will always be the last to join during initialization.
    1097           0 :          */
    1098           0 :         if (!m_initialLoadComplete)
    1099           0 :         {
    1100             :             if (m_mucRoom->nick() == nick)
    1101             :                 m_initialLoadComplete = true;
    1102           0 :         }
    1103             :         else if (it == m_PlayerMap.end())
    1104             :         {
    1105             :             CreateGUIMessage(
    1106           0 :                 "chat",
    1107             :                 "join",
    1108             :                 std::time(nullptr),
    1109           0 :                 "nick", nick);
    1110           0 :         }
    1111           0 :         else if (it->second.m_Role != participant.role)
    1112             :         {
    1113             :             CreateGUIMessage(
    1114           0 :                 "chat",
    1115             :                 "role",
    1116           0 :                 std::time(nullptr),
    1117           0 :                 "nick", nick,
    1118             :                 "oldrole", it->second.m_Role,
    1119           0 :                 "newrole", participant.role);
    1120             :         }
    1121             :         else
    1122             :         {
    1123           0 :             // Don't create a GUI message for regular presence changes, because
    1124           0 :             // several hundreds of them accumulate during a match, impacting performance terribly and
    1125             :             // the only way they are used is to determine whether to update the playerlist.
    1126           0 :         }
    1127             : 
    1128           0 :         DbgXMPP(
    1129             :             nick << " is in the room, "
    1130           0 :             "presence: " << GetPresenceString(presence.presence()) << ", "
    1131             :             "role: "<< GetRoleString(participant.role));
    1132             : 
    1133             :         if (it == m_PlayerMap.end())
    1134           0 :         {
    1135           0 :             m_PlayerMap.emplace(
    1136           0 :                 std::piecewise_construct,
    1137           0 :                 std::forward_as_tuple(nick),
    1138           0 :                 std::forward_as_tuple(presence.presence(), participant.role, std::string()));
    1139             :         }
    1140           0 :         else
    1141           0 :         {
    1142           0 :             it->second.m_Presence = presence.presence();
    1143           0 :             it->second.m_Role = participant.role;
    1144             :         }
    1145           0 :     }
    1146           0 : 
    1147             :     m_PlayerMapUpdate = true;
    1148           0 : }
    1149           0 : 
    1150           0 : /**
    1151             :  * Update local cache when subject changes.
    1152             :  */
    1153             : void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::string& nick, const glooxwrapper::string& subject)
    1154             : {
    1155           0 :     m_Subject = wstring_from_utf8(subject.to_string());
    1156             : 
    1157             :     CreateGUIMessage(
    1158           0 :         "chat",
    1159             :         "subject",
    1160             :         std::time(nullptr),
    1161           0 :         "nick", nick,
    1162             :         "subject", m_Subject);
    1163           0 : }
    1164           0 : 
    1165             : /**
    1166           0 :  * Get current subject.
    1167             :  */
    1168           0 : const std::wstring& XmppClient::GetSubject()
    1169           0 : {
    1170             :     return m_Subject;
    1171           0 : }
    1172           0 : 
    1173           0 : /**
    1174             :  * Request nick change, real change via mucRoomHandler.
    1175             :  *
    1176           0 :  * @param nick Desired nickname
    1177             :  */
    1178             : void XmppClient::SetNick(const std::string& nick)
    1179             : {
    1180             :     m_mucRoom->setNick(nick);
    1181             : }
    1182           0 : 
    1183             : /**
    1184           0 :  * Get current nickname.
    1185           0 :  */
    1186             : std::string XmppClient::GetNick() const
    1187             : {
    1188             :     return m_mucRoom->nick().to_string();
    1189             : }
    1190           0 : 
    1191             : std::string XmppClient::GetJID() const
    1192           0 : {
    1193             :     return m_client->getJID().to_string();
    1194           0 : }
    1195             : 
    1196             : /**
    1197             :  * Kick a player from the current room.
    1198           0 :  *
    1199           0 :  * @param nick Nickname to be kicked
    1200           0 :  * @param reason Reason the player was kicked
    1201             :  */
    1202             : void XmppClient::kick(const std::string& nick, const std::string& reason)
    1203             : {
    1204             :     m_mucRoom->kick(nick, reason);
    1205           0 : }
    1206             : 
    1207             : /**
    1208           0 :  * Ban a player from the current room.
    1209             :  *
    1210           0 :  * @param nick Nickname to be banned
    1211             :  * @param reason Reason the player was banned
    1212             :  */
    1213             : void XmppClient::ban(const std::string& nick, const std::string& reason)
    1214           0 : {
    1215           0 :     m_mucRoom->ban(nick, reason);
    1216           0 : }
    1217             : 
    1218             : /**
    1219             :  * Change the xmpp presence of the client.
    1220             :  *
    1221           0 :  * @param presence A string containing the desired presence
    1222             :  */
    1223           0 : void XmppClient::SetPresence(const std::string& presence)
    1224             : {
    1225           0 : #define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
    1226             :     IF("available", Available);
    1227           0 :     else IF("chat", Chat);
    1228           0 :     else IF("away", Away);
    1229           0 :     else IF("playing", DND);
    1230           0 :     else IF("offline", Unavailable);
    1231           0 :     // The others are not to be set
    1232             : #undef IF
    1233           0 :     else LOGERROR("Unknown presence '%s'", presence.c_str());
    1234             : }
    1235             : 
    1236           0 : /**
    1237           0 :  * Get the current xmpp presence of the given nick.
    1238           0 :  */
    1239             : const char* XmppClient::GetPresence(const std::string& nick)
    1240             : {
    1241           0 :     const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1242           0 : 
    1243           0 :     if (it == m_PlayerMap.end())
    1244             :         return "offline";
    1245             : 
    1246           0 :     return GetPresenceString(it->second.m_Presence);
    1247             : }
    1248           0 : 
    1249           0 : /**
    1250             :  * Get the current xmpp role of the given nick.
    1251             :  */
    1252           0 : const char* XmppClient::GetRole(const std::string& nick)
    1253           0 : {
    1254             :     const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1255           0 : 
    1256             :     if (it == m_PlayerMap.end())
    1257           0 :         return "";
    1258             : 
    1259           0 :     return GetRoleString(it->second.m_Role);
    1260             : }
    1261           0 : 
    1262           0 : /**
    1263             :  * Get the most recent received rating of the given nick.
    1264           0 :  * Notice that this doesn't request a rating profile if it hasn't been received yet.
    1265           0 :  */
    1266             : std::wstring XmppClient::GetRating(const std::string& nick)
    1267           0 : {
    1268           0 :     const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1269             : 
    1270           0 :     if (it == m_PlayerMap.end())
    1271           0 :         return std::wstring();
    1272             : 
    1273             :     return wstring_from_utf8(it->second.m_Rating.to_string());
    1274           0 : }
    1275           0 : 
    1276           0 : /*****************************************************
    1277             :  * Utilities                                         *
    1278           0 :  *****************************************************/
    1279           0 : 
    1280             : /**
    1281           0 :  * Parse and return the timestamp of a historic chat message and return the current time for new chat messages.
    1282             :  * Historic chat messages are implement as DelayedDelivers as specified in XEP-0203.
    1283           0 :  * Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
    1284             :  *
    1285           0 :  * @returns Seconds since the epoch.
    1286             :  */
    1287           0 : std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg)
    1288           0 : {
    1289           0 :     // Only historic messages contain a timestamp!
    1290             :     if (!msg.when())
    1291           0 :         return std::time(nullptr);
    1292           0 : 
    1293             :     // The locale is irrelevant, because the XMPP date format doesn't contain written month names
    1294           0 :     for (const std::string& format : std::vector<std::string>{ "Y-M-d'T'H:m:sZ", "Y-M-d'T'H:m:s.SZ" })
    1295             :     {
    1296           0 :         UDate dateTime = g_L10n.ParseDateTime(msg.when()->stamp().to_string(), format, icu::Locale::getUS());
    1297             :         if (dateTime)
    1298           0 :             return dateTime / 1000.0;
    1299             :     }
    1300           0 : 
    1301           0 :     return std::time(nullptr);
    1302             : }
    1303           0 : 
    1304           0 : /**
    1305             :  * Convert a gloox presence type to an untranslated string literal to be used as an identifier by the scripts.
    1306             :  */
    1307           0 : const char* XmppClient::GetPresenceString(const gloox::Presence::PresenceType presenceType)
    1308             : {
    1309             :     switch (presenceType)
    1310           0 :     {
    1311             : #define CASE(X,Y) case gloox::Presence::X: return Y
    1312           0 :     CASE(Available, "available");
    1313           0 :     CASE(Chat, "chat");
    1314           0 :     CASE(Away, "away");
    1315             :     CASE(DND, "playing");
    1316           0 :     CASE(XA, "away");
    1317           0 :     CASE(Unavailable, "offline");
    1318             :     CASE(Probe, "probe");
    1319           0 :     CASE(Error, "error");
    1320             :     CASE(Invalid, "invalid");
    1321             :     default:
    1322           0 :         LOGERROR("Unknown presence type '%d'", static_cast<int>(presenceType));
    1323             :         return "";
    1324           0 : #undef CASE
    1325           0 :     }
    1326             : }
    1327           0 : 
    1328             : /**
    1329           0 :  * Convert a gloox role type to an untranslated string literal to be used as an identifier by the scripts.
    1330           0 :  */
    1331             : const char* XmppClient::GetRoleString(const gloox::MUCRoomRole role)
    1332           0 : {
    1333           0 :     switch (role)
    1334             :     {
    1335           0 : #define CASE(X, Y) case gloox::X: return Y
    1336             :     CASE(RoleNone, "none");
    1337             :     CASE(RoleVisitor, "visitor");
    1338           0 :     CASE(RoleParticipant, "participant");
    1339             :     CASE(RoleModerator, "moderator");
    1340           0 :     CASE(RoleInvalid, "invalid");
    1341           0 :     default:
    1342             :         LOGERROR("Unknown role type '%d'", static_cast<int>(role));
    1343           0 :         return "";
    1344           0 : #undef CASE
    1345             :     }
    1346           0 : }
    1347           0 : 
    1348           0 : /**
    1349             :  * Translates a gloox certificate error codes, i.e. gloox certificate statuses except CertOk.
    1350           0 :  * Keep in sync with specifications.
    1351             :  */
    1352           0 : std::string XmppClient::CertificateErrorToString(gloox::CertStatus status)
    1353           0 : {
    1354             :     std::map<gloox::CertStatus, std::string> certificateErrorStrings = {
    1355           0 :         { gloox::CertInvalid, g_L10n.Translate("The certificate is not trusted.") },
    1356             :         { gloox::CertSignerUnknown, g_L10n.Translate("The certificate hasn't got a known issuer.") },
    1357           0 :         { gloox::CertRevoked, g_L10n.Translate("The certificate has been revoked.") },
    1358           0 :         { gloox::CertExpired, g_L10n.Translate("The certificate has expired.") },
    1359           0 :         { gloox::CertNotActive, g_L10n.Translate("The certificate is not yet active.") },
    1360             :         { gloox::CertWrongPeer, g_L10n.Translate("The certificate has not been issued for the peer connected to.") },
    1361           0 :         { gloox::CertSignerNotCa, g_L10n.Translate("The certificate signer is not a certificate authority.") }
    1362             :     };
    1363           0 : 
    1364           0 :     std::string result;
    1365             : 
    1366           0 :     for (std::map<gloox::CertStatus, std::string>::iterator it = certificateErrorStrings.begin(); it != certificateErrorStrings.end(); ++it)
    1367             :         if (status & it->first)
    1368           0 :             result += "\n" + it->second;
    1369           0 : 
    1370           0 :     return result;
    1371             : }
    1372           0 : 
    1373             : /**
    1374           0 :  * Convert a gloox stanza error type to string.
    1375           0 :  * Keep in sync with Gloox documentation
    1376             :  *
    1377             :  * @param err Error to be converted
    1378           0 :  * @return Converted error string
    1379           0 :  */
    1380             : std::string XmppClient::StanzaErrorToString(gloox::StanzaError err)
    1381           0 : {
    1382             : #define CASE(X, Y) case gloox::X: return Y
    1383           0 : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
    1384           0 :     switch (err)
    1385           0 :     {
    1386           0 :     CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
    1387             :     DEBUG_CASE(StanzaErrorBadRequest, "Server received malformed XML");
    1388             :     CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in"));
    1389             :     DEBUG_CASE(StanzaErrorFeatureNotImplemented, "Server does not implement requested feature");
    1390           0 :     CASE(StanzaErrorForbidden, g_L10n.Translate("Forbidden"));
    1391           0 :     DEBUG_CASE(StanzaErrorGone, "Unable to find message receipiant");
    1392             :     CASE(StanzaErrorInternalServerError, g_L10n.Translate("Internal server error"));
    1393           0 :     DEBUG_CASE(StanzaErrorItemNotFound, "Message receipiant does not exist");
    1394           0 :     DEBUG_CASE(StanzaErrorJidMalformed, "JID (XMPP address) malformed");
    1395           0 :     DEBUG_CASE(StanzaErrorNotAcceptable, "Receipiant refused message. Possible policy issue");
    1396           0 :     CASE(StanzaErrorNotAllowed, g_L10n.Translate("Not allowed"));
    1397             :     CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized"));
    1398             :     DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request");
    1399           0 :     DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment");
    1400             :     CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable"));
    1401             :     DEBUG_CASE(StanzaErrorRedirect, "Request redirected");
    1402           0 :     CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required"));
    1403             :     DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
    1404           0 :     DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out");
    1405             :     DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints");
    1406             :     CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable"));
    1407             :     DEBUG_CASE(StanzaErrorSubscribtionRequired, "Service requires subscription");
    1408           0 :     DEBUG_CASE(StanzaErrorUnexpectedRequest, "Attempt to send from invalid stanza address");
    1409           0 :     DEBUG_CASE(StanzaErrorUnknownSender, "Invalid 'from' address");
    1410             :     default:
    1411             :         return g_L10n.Translate("Unknown error");
    1412           0 :     }
    1413           0 : #undef DEBUG_CASE
    1414             : #undef CASE
    1415             : }
    1416             : 
    1417             : /**
    1418             :  * Convert a gloox connection error enum to string
    1419             :  * Keep in sync with Gloox documentation
    1420             :  *
    1421             :  * @param err Error to be converted
    1422           0 :  * @return Converted error string
    1423             :  */
    1424           0 : std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err)
    1425             : {
    1426           0 : #define CASE(X, Y) case gloox::X: return Y
    1427             : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
    1428           0 :     switch (err)
    1429             :     {
    1430             :     CASE(ConnNoError, g_L10n.Translate("No error"));
    1431           0 :     CASE(ConnStreamError, g_L10n.Translate("Stream error"));
    1432           0 :     CASE(ConnStreamVersionError, g_L10n.Translate("The incoming stream version is unsupported"));
    1433             :     CASE(ConnStreamClosed, g_L10n.Translate("The stream has been closed by the server"));
    1434           0 :     DEBUG_CASE(ConnProxyAuthRequired, "The HTTP/SOCKS5 proxy requires authentication");
    1435           0 :     DEBUG_CASE(ConnProxyAuthFailed, "HTTP/SOCKS5 proxy authentication failed");
    1436             :     DEBUG_CASE(ConnProxyNoSupportedAuth, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism");
    1437           0 :     CASE(ConnIoError, g_L10n.Translate("An I/O error occurred"));
    1438             :     DEBUG_CASE(ConnParseError, "An XML parse error occurred");
    1439           0 :     CASE(ConnConnectionRefused, g_L10n.Translate("The connection was refused by the server"));
    1440           0 :     CASE(ConnDnsError, g_L10n.Translate("Resolving the server's hostname failed"));
    1441             :     CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory"));
    1442             :     DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
    1443             :     CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
    1444             :     CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption"));
    1445             :     DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed");
    1446             :     CASE(ConnAuthenticationFailed, g_L10n.Translate("Authentication failed. Incorrect password or account does not exist"));
    1447           0 :     CASE(ConnUserDisconnected, g_L10n.Translate("The user or system requested a disconnect"));
    1448             :     CASE(ConnNotConnected, g_L10n.Translate("There is no active connection"));
    1449           0 :     default:
    1450           0 :         return g_L10n.Translate("Unknown error");
    1451             :     }
    1452             : #undef DEBUG_CASE
    1453             : #undef CASE
    1454             : }
    1455           0 : 
    1456             : /**
    1457           0 :  * Convert a gloox registration result enum to string
    1458             :  * Keep in sync with Gloox documentation
    1459           0 :  *
    1460           0 :  * @param err Enum to be converted
    1461             :  * @return Converted string
    1462             :  */
    1463             : std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res)
    1464             : {
    1465           0 : #define CASE(X, Y) case gloox::X: return Y
    1466             : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
    1467             :     switch (res)
    1468             :     {
    1469           0 :     CASE(RegistrationSuccess, g_L10n.Translate("Your account has been successfully registered"));
    1470           0 :     CASE(RegistrationNotAcceptable, g_L10n.Translate("Not all necessary information provided"));
    1471             :     CASE(RegistrationConflict, g_L10n.Translate("Username already exists"));
    1472             :     DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change");
    1473             :     DEBUG_CASE(RegistrationBadRequest, "Server received an incomplete request");
    1474             :     DEBUG_CASE(RegistrationForbidden, "Registration forbidden");
    1475             :     DEBUG_CASE(RegistrationRequired, "Account cannot be removed as it does not exist");
    1476           0 :     DEBUG_CASE(RegistrationUnexpectedRequest, "This client is unregistered with the server");
    1477             :     DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes");
    1478             :     default:
    1479             :         return "";
    1480           0 :     }
    1481             : #undef DEBUG_CASE
    1482             : #undef CASE
    1483             : }
    1484             : 
    1485             : void XmppClient::SendStunEndpointToHost(const std::string& ip, u16 port, const std::string& hostJIDStr)
    1486           0 : {
    1487             :     DbgXMPP("SendStunEndpointToHost " << hostJIDStr);
    1488           0 : 
    1489           0 :     glooxwrapper::JID hostJID(hostJIDStr);
    1490             :     glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
    1491           0 :     session.sessionInitiate(ip.c_str(), port);
    1492             : }
    1493           0 : 
    1494             : void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle)
    1495             : {
    1496             :     if (action == gloox::Jingle::SessionInitiate)
    1497             :         handleSessionInitiation(session, jingle);
    1498             : }
    1499           0 : 
    1500             : void XmppClient::handleSessionInitiation(glooxwrapper::Jingle::Session& UNUSED(session), const glooxwrapper::Jingle::Session::Jingle& jingle)
    1501           0 : {
    1502             :     glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle.getCandidate();
    1503             : 
    1504             :     if (candidate.ip.empty())
    1505             :     {
    1506           0 :         LOGERROR("Failed to retrieve Jingle candidate");
    1507           0 :         return;
    1508             :     }
    1509             : 
    1510             :     if (!g_NetServer)
    1511             :     {
    1512             :         LOGERROR("Received STUN connection request, but not hosting currently!");
    1513             :         return;
    1514             :     }
    1515             : 
    1516             :     g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
    1517             : }

Generated by: LCOV version 1.13