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: 1 666 0.2 %
Date: 2023-01-19 00:18:29 Functions: 2 88 2.3 %

          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             :     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             : 
     202           0 : void XmppClient::TraceMember(JSTracer* trc)
     203             : {
     204           0 :     for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
     205           0 :         JS::TraceEdge(trc, &guiMessage, "m_GuiMessageQueue");
     206             : 
     207           0 :     for (JS::Heap<JS::Value>& guiMessage : m_HistoricGuiMessages)
     208           0 :         JS::TraceEdge(trc, &guiMessage, "m_HistoricGuiMessages");
     209           0 : }
     210             : 
     211             : /// Network
     212           0 : void XmppClient::connect()
     213             : {
     214           0 :     m_initialLoadComplete = false;
     215           0 :     m_client->connect(false);
     216           0 : }
     217             : 
     218           0 : void XmppClient::disconnect()
     219             : {
     220           0 :     m_client->disconnect();
     221           0 : }
     222             : 
     223           0 : bool XmppClient::isConnected()
     224             : {
     225           0 :     return m_isConnected;
     226             : }
     227             : 
     228           0 : void XmppClient::recv()
     229             : {
     230           0 :     m_client->recv(1);
     231           0 : }
     232             : 
     233             : /**
     234             :  * Log (debug) Handler
     235             :  */
     236           0 : void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
     237             : {
     238           0 :     std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
     239           0 : }
     240             : 
     241             : /*****************************************************
     242             :  * Connection handlers                               *
     243             :  *****************************************************/
     244             : 
     245             : /**
     246             :  * Handle connection
     247             :  */
     248           0 : void XmppClient::onConnect()
     249             : {
     250           0 :     if (m_mucRoom)
     251             :     {
     252           0 :         m_isConnected = true;
     253           0 :         CreateGUIMessage("system", "connected", std::time(nullptr));
     254           0 :         m_mucRoom->join();
     255             :     }
     256             : 
     257           0 :     if (m_registration)
     258           0 :         m_registration->fetchRegistrationFields();
     259           0 : }
     260             : 
     261             : /**
     262             :  * Handle disconnection
     263             :  */
     264           0 : void XmppClient::onDisconnect(gloox::ConnectionError error)
     265             : {
     266             :     // Make sure we properly leave the room so that
     267             :     // everything works if we decide to come back later
     268           0 :     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           0 :         glooxwrapper::Tag::free(t);
     274           0 :     for (const glooxwrapper::Tag* const& t : m_BoardList)
     275           0 :         glooxwrapper::Tag::free(t);
     276           0 :     for (const glooxwrapper::Tag* const& t : m_Profile)
     277           0 :         glooxwrapper::Tag::free(t);
     278             : 
     279           0 :     m_BoardList.clear();
     280           0 :     m_GameList.clear();
     281           0 :     m_PlayerMap.clear();
     282           0 :     m_PlayerMapUpdate = true;
     283           0 :     m_Profile.clear();
     284           0 :     m_HistoricGuiMessages.clear();
     285           0 :     m_isConnected = false;
     286           0 :     m_initialLoadComplete = false;
     287             : 
     288           0 :     CreateGUIMessage(
     289             :         "system",
     290             :         "disconnected",
     291             :         std::time(nullptr),
     292             :         "reason", error,
     293             :         "certificate_status", m_certStatus);
     294           0 : }
     295             : 
     296             : /**
     297             :  * Handle TLS connection.
     298             :  */
     299           0 : bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
     300             : {
     301             :     DbgXMPP("onTLSConnect");
     302             :     DbgXMPP(
     303             :         "status: " << info.status <<
     304             :         "\nissuer: " << info.issuer <<
     305             :         "\npeer: " << info.server <<
     306             :         "\nprotocol: " << info.protocol <<
     307             :         "\nmac: " << info.mac <<
     308             :         "\ncipher: " << info.cipher <<
     309             :         "\ncompression: " << info.compression );
     310             : 
     311           0 :     m_certStatus = static_cast<gloox::CertStatus>(info.status);
     312             : 
     313             :     // Optionally accept invalid certificates, see require_tls option.
     314           0 :     bool verify_certificate = true;
     315           0 :     CFG_GET_VAL("lobby.verify_certificate", verify_certificate);
     316             : 
     317           0 :     return info.status == gloox::CertOk || !verify_certificate;
     318             : }
     319             : 
     320             : /**
     321             :  * Handle MUC room errors
     322             :  */
     323           0 : void XmppClient::handleMUCError(glooxwrapper::MUCRoom& UNUSED(room), gloox::StanzaError err)
     324             : {
     325             :     DbgXMPP("MUC Error " << ": " << StanzaErrorToString(err));
     326           0 :     CreateGUIMessage("system", "error", std::time(nullptr), "text", err);
     327           0 : }
     328             : 
     329             : /*****************************************************
     330             :  * Requests to server                                *
     331             :  *****************************************************/
     332             : 
     333             : /**
     334             :  * Request the leaderboard data from the server.
     335             :  */
     336           0 : void XmppClient::SendIqGetBoardList()
     337             : {
     338           0 :     glooxwrapper::JID echelonJid(m_echelonId);
     339             : 
     340             :     // Send IQ
     341           0 :     BoardListQuery* b = new BoardListQuery();
     342           0 :     b->m_Command = "getleaderboard";
     343           0 :     glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
     344           0 :     iq.addExtension(b);
     345             :     DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
     346           0 :     m_client->send(iq);
     347           0 : }
     348             : 
     349             : /**
     350             :  * Request the profile data from the server.
     351             :  */
     352           0 : void XmppClient::SendIqGetProfile(const std::string& player)
     353             : {
     354           0 :     glooxwrapper::JID echelonJid(m_echelonId);
     355             : 
     356             :     // Send IQ
     357           0 :     ProfileQuery* b = new ProfileQuery();
     358           0 :     b->m_Command = player;
     359           0 :     glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID());
     360           0 :     iq.addExtension(b);
     361             :     DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
     362           0 :     m_client->send(iq);
     363           0 : }
     364             : 
     365             : /**
     366             :  * Request the Connection data (ip, port...) from the server.
     367             :  */
     368           0 : void XmppClient::SendIqGetConnectionData(const std::string& jid, const std::string& password, const std::string& clientSalt, bool localIP)
     369             : {
     370           0 :     glooxwrapper::JID targetJID(jid);
     371             : 
     372           0 :     ConnectionData* connectionData = new ConnectionData();
     373           0 :     connectionData->m_Password = password;
     374           0 :     connectionData->m_ClientSalt = clientSalt;
     375           0 :     connectionData->m_IsLocalIP = localIP ? "1" : "0";
     376           0 :     glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID());
     377           0 :     iq.addExtension(connectionData);
     378           0 :     m_connectionDataJid = iq.from().full();
     379           0 :     m_connectionDataIqId = iq.id().to_string();
     380             :     DbgXMPP("SendIqGetConnectionData [" << tag_xml(iq) << "]");
     381           0 :     m_client->send(iq);
     382           0 : }
     383             : 
     384             : /**
     385             :  * Send game report containing numerous game properties to the server.
     386             :  *
     387             :  * @param data A JS array of game statistics
     388             :  */
     389           0 : void XmppClient::SendIqGameReport(const ScriptRequest& rq, JS::HandleValue data)
     390             : {
     391           0 :     glooxwrapper::JID echelonJid(m_echelonId);
     392             : 
     393             :     // Setup some base stanza attributes
     394           0 :     GameReport* game = new GameReport();
     395           0 :     glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
     396             : 
     397             :     // Iterate through all the properties reported and add them to the stanza.
     398           0 :     std::vector<std::string> properties;
     399           0 :     Script::EnumeratePropertyNames(rq, data, true, properties);
     400           0 :     for (const std::string& p : properties)
     401             :     {
     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             :     }
     406             : 
     407             :     // Add stanza to IQ
     408           0 :     game->m_GameReport.emplace_back(report);
     409             : 
     410             :     // Send IQ
     411           0 :     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           0 : };
     416             : 
     417             : /**
     418             :  * Send a request to register a game to the server.
     419             :  *
     420             :  * @param data A JS array of game attributes
     421             :  */
     422           0 : void XmppClient::SendIqRegisterGame(const ScriptRequest& rq, JS::HandleValue data)
     423             : {
     424           0 :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     425             : 
     426             :     // Setup some base stanza attributes
     427           0 :     std::unique_ptr<GameListQuery> g = std::make_unique<GameListQuery>();
     428           0 :     g->m_Command = "register";
     429           0 :     glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
     430             : 
     431             :     // Iterate through all the properties reported and add them to the stanza.
     432           0 :     std::vector<std::string> properties;
     433           0 :     Script::EnumeratePropertyNames(rq, data, true, properties);
     434           0 :     for (const std::string& p : properties)
     435             :     {
     436           0 :         std::string value;
     437           0 :         if (!Script::GetProperty(rq, data, p.c_str(), value))
     438             :         {
     439           0 :             LOGERROR("Could not parse attribute '%s' as string.", p);
     440           0 :             return;
     441             :         }
     442           0 :         game->addAttribute(p, value);
     443             :     }
     444             : 
     445             :     // Overwrite some attributes to make it slightly less trivial to do bad things,
     446             :     // and explicit some invariants.
     447             : 
     448             :     // The JID must point to ourself.
     449           0 :     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             :     DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
     458           0 :     m_client->send(iq);
     459             : }
     460             : 
     461             : /**
     462             :  * Send a request to unregister a game to the server.
     463             :  */
     464           0 : void XmppClient::SendIqUnregisterGame()
     465             : {
     466           0 :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     467             : 
     468             :     // Send IQ
     469           0 :     GameListQuery* g = new GameListQuery();
     470           0 :     g->m_Command = "unregister";
     471           0 :     g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game"));
     472             : 
     473           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
     474           0 :     iq.addExtension(g);
     475             :     DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
     476           0 :     m_client->send(iq);
     477           0 : }
     478             : 
     479             : /**
     480             :  * Send a request to change the state of a registered game on the server.
     481             :  *
     482             :  * A game can either be in the 'running' or 'waiting' state - the server
     483             :  * decides which - but we need to update the current players that are
     484             :  * 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           0 :     glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     489             : 
     490             :     // Send IQ
     491           0 :     GameListQuery* g = new GameListQuery();
     492           0 :     g->m_Command = "changestate";
     493           0 :     glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
     494           0 :     game->addAttribute("nbp", nbp);
     495           0 :     game->addAttribute("players", players);
     496           0 :     g->m_GameList.emplace_back(game);
     497             : 
     498           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
     499           0 :     iq.addExtension(g);
     500             :     DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
     501           0 :     m_client->send(iq);
     502           0 : }
     503             : 
     504             : /*****************************************************
     505             :  * iq to clients                                     *
     506             :  *****************************************************/
     507             : 
     508             : /**
     509             :  * Send lobby authentication token.
     510             :  */
     511           0 : 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             : 
     516           0 :     glooxwrapper::JID clientJid(to);
     517           0 :     glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID());
     518           0 :     iq.addExtension(auth);
     519             :     DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]");
     520           0 :     m_client->send(iq);
     521           0 : }
     522             : 
     523             : /*****************************************************
     524             :  * Account registration                              *
     525             :  *****************************************************/
     526             : 
     527           0 : void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
     528             : {
     529           0 :     glooxwrapper::RegistrationFields vals;
     530           0 :     vals.username = m_username;
     531           0 :     vals.password = m_password;
     532           0 :     m_registration->createAccount(fields, vals);
     533           0 : }
     534             : 
     535           0 : void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
     536             : {
     537           0 :     if (result == gloox::RegistrationSuccess)
     538           0 :         CreateGUIMessage("system", "registered", std::time(nullptr));
     539             :     else
     540           0 :         CreateGUIMessage("system", "error", std::time(nullptr), "text", result);
     541             : 
     542           0 :     disconnect();
     543           0 : }
     544             : 
     545           0 : void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
     546             : {
     547             :     DbgXMPP("the account already exists");
     548           0 : }
     549             : 
     550           0 : void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
     551             : {
     552             :     DbgXMPP("dataForm received");
     553           0 : }
     554             : 
     555           0 : void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
     556             : {
     557             :     DbgXMPP("OOB registration requested");
     558           0 : }
     559             : 
     560             : /*****************************************************
     561             :  * Requests from GUI                                 *
     562             :  *****************************************************/
     563             : 
     564             : /**
     565             :  * 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           0 : JS::Value XmppClient::GUIGetPlayerList(const ScriptRequest& rq)
     570             : {
     571           0 :     JS::RootedValue ret(rq.cx);
     572           0 :     Script::CreateArray(rq, &ret);
     573           0 :     int j = 0;
     574             : 
     575           0 :     for (const std::pair<const glooxwrapper::string, SPlayer>& p : m_PlayerMap)
     576             :     {
     577           0 :         JS::RootedValue player(rq.cx);
     578             : 
     579           0 :         Script::CreateObject(
     580             :             rq,
     581             :             &player,
     582             :             "name", p.first,
     583             :             "presence", p.second.m_Presence,
     584             :             "rating", p.second.m_Rating,
     585             :             "role", p.second.m_Role);
     586             : 
     587           0 :         Script::SetPropertyInt(rq, ret, j++, player);
     588             :     }
     589           0 :     return ret;
     590             : }
     591             : 
     592             : /**
     593             :  * Handle requests from the GUI for the list of all active games.
     594             :  *
     595             :  * @return A JS array containing all known games
     596             :  */
     597           0 : JS::Value XmppClient::GUIGetGameList(const ScriptRequest& rq)
     598             : {
     599           0 :     JS::RootedValue ret(rq.cx);
     600           0 :     Script::CreateArray(rq, &ret);
     601           0 :     int j = 0;
     602             : 
     603           0 :     const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword",
     604             :         "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
     605             :         "victoryConditions", "startTime", "mods" };
     606             : 
     607           0 :     for(const glooxwrapper::Tag* const& t : m_GameList)
     608             :     {
     609           0 :         JS::RootedValue game(rq.cx);
     610           0 :         Script::CreateObject(rq, &game);
     611             : 
     612           0 :         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             :     }
     617           0 :     return ret;
     618             : }
     619             : 
     620             : /**
     621             :  * Handle requests from the GUI for leaderboard data.
     622             :  *
     623             :  * @return A JS array containing all known leaderboard data
     624             :  */
     625           0 : JS::Value XmppClient::GUIGetBoardList(const ScriptRequest& rq)
     626             : {
     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           0 :         JS::RootedValue board(rq.cx);
     636           0 :         Script::CreateObject(rq, &board);
     637             : 
     638           0 :         for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
     639           0 :             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             : }
     645             : 
     646             : /**
     647             :  * 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           0 :     int j = 0;
     656             : 
     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           0 :         Script::CreateObject(rq, &profile);
     663             : 
     664           0 :         for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
     665           0 :             Script::SetProperty(rq, profile, stats[i], t->findAttribute(stats[i]));
     666             : 
     667           0 :         Script::SetPropertyInt(rq, ret, j++, profile);
     668             :     }
     669           0 :     return ret;
     670             : }
     671             : 
     672             : /*****************************************************
     673             :  * Message interfaces                                *
     674             :  *****************************************************/
     675             : 
     676           0 : void SetGUIMessageProperty(const ScriptRequest& UNUSED(rq), JS::HandleObject UNUSED(messageObj))
     677             : {
     678           0 : }
     679             : 
     680             : template<typename T, typename... Args>
     681           0 : void SetGUIMessageProperty(const ScriptRequest& rq, JS::HandleObject messageObj, const std::string& propertyName, const T& propertyValue, Args const&... args)
     682             : {
     683           0 :     JS::RootedValue scriptPropertyValue(rq.cx);
     684           0 :     Script::ToJSVal(rq, &scriptPropertyValue, propertyValue);
     685           0 :     JS_DefineProperty(rq.cx, messageObj, propertyName.c_str(), scriptPropertyValue, JSPROP_ENUMERATE);
     686           0 :     SetGUIMessageProperty(rq, messageObj, args...);
     687           0 : }
     688             : 
     689             : template<typename... Args>
     690           0 : void XmppClient::CreateGUIMessage(
     691             :     const std::string& type,
     692             :     const std::string& level,
     693             :     const std::time_t time,
     694             :     Args const&... args)
     695             : {
     696           0 :     if (!m_ScriptInterface)
     697           0 :         return;
     698           0 :     ScriptRequest rq(m_ScriptInterface);
     699           0 :     JS::RootedValue message(rq.cx);
     700           0 :     Script::CreateObject(
     701             :         rq,
     702             :         &message,
     703             :         "type", type,
     704             :         "level", level,
     705             :         "historic", false,
     706             :         "time", static_cast<double>(time));
     707             : 
     708           0 :     JS::RootedObject messageObj(rq.cx, message.toObjectOrNull());
     709           0 :     SetGUIMessageProperty(rq, messageObj, args...);
     710           0 :     Script::FreezeObject(rq, message, true);
     711           0 :     m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
     712             : }
     713             : 
     714           0 : 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           0 :     if (!m_initialLoadComplete)
     719           0 :         return false;
     720             : 
     721           0 :     bool hasUpdate = m_PlayerMapUpdate;
     722           0 :     m_PlayerMapUpdate = false;
     723           0 :     return hasUpdate;
     724             : }
     725             : 
     726           0 : JS::Value XmppClient::GuiPollNewMessages(const ScriptInterface& guiInterface)
     727             : {
     728           0 :     if ((m_isConnected && !m_initialLoadComplete) || m_GuiMessageQueue.empty())
     729           0 :         return JS::UndefinedValue();
     730             : 
     731           0 :     ScriptRequest rq(m_ScriptInterface);
     732             : 
     733             :     // Optimize for batch message processing that is more
     734             :     // performance demanding than processing a lone message.
     735           0 :     JS::RootedValue messages(rq.cx);
     736           0 :     Script::CreateArray(rq, &messages);
     737             : 
     738           0 :     int j = 0;
     739             : 
     740           0 :     for (const JS::Heap<JS::Value>& message : m_GuiMessageQueue)
     741             :     {
     742           0 :         Script::SetPropertyInt(rq, messages, j++, message);
     743             : 
     744             :         // Store historic chat messages.
     745             :         // Only store relevant messages to minimize memory footprint.
     746           0 :         JS::RootedValue rootedMessage(rq.cx, message);
     747           0 :         std::string type;
     748           0 :         Script::GetProperty(rq, rootedMessage, "type", type);
     749           0 :         if (type != "chat")
     750           0 :             continue;
     751             : 
     752           0 :         std::string level;
     753           0 :         Script::GetProperty(rq, rootedMessage, "level", level);
     754           0 :         if (level != "room-message" && level != "private-message")
     755           0 :             continue;
     756             : 
     757           0 :         JS::RootedValue historicMessage(rq.cx, Script::DeepCopy(rq, rootedMessage));
     758             :         if (true)
     759             :         {
     760           0 :             Script::SetProperty(rq, historicMessage, "historic", true);
     761           0 :             Script::FreezeObject(rq, historicMessage, true);
     762           0 :             m_HistoricGuiMessages.push_back(JS::Heap<JS::Value>(historicMessage));
     763             :         }
     764             :         else
     765             :             LOGERROR("Could not clone historic lobby GUI message!");
     766             :     }
     767           0 :     m_GuiMessageQueue.clear();
     768             : 
     769             :     // Copy the messages over to the caller script interface.
     770           0 :     return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
     771             : }
     772             : 
     773           0 : JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& guiInterface)
     774             : {
     775           0 :     if (m_HistoricGuiMessages.empty())
     776           0 :         return JS::UndefinedValue();
     777             : 
     778           0 :     ScriptRequest rq(m_ScriptInterface);
     779             : 
     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             : 
     787             :     // Copy the messages over to the caller script interface.
     788           0 :     return Script::CloneValueFromOtherCompartment(guiInterface, *m_ScriptInterface, messages);
     789             : }
     790             : 
     791             : /**
     792             :  * Send a standard MUC textual message.
     793             :  */
     794           0 : void XmppClient::SendMUCMessage(const std::string& message)
     795             : {
     796           0 :     m_mucRoom->send(message);
     797           0 : }
     798             : 
     799             : /**
     800             :  * Handle a room message.
     801             :  */
     802           0 : void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::Message& msg, bool priv)
     803             : {
     804             :     DbgXMPP(msg.from().resource() << " said " << msg.body());
     805             : 
     806           0 :     CreateGUIMessage(
     807             :         "chat",
     808             :         priv ? "private-message" : "room-message",
     809             :         ComputeTimestamp(msg),
     810           0 :         "from", msg.from().resource(),
     811           0 :         "text", msg.body());
     812           0 : }
     813             : 
     814             : /**
     815             :  * Handle a private message.
     816             :  */
     817           0 : void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession*)
     818             : {
     819             :     DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
     820             :       << ", message " << msg.body() << ", thread id " << msg.thread());
     821             : 
     822           0 :     CreateGUIMessage(
     823             :         "chat",
     824             :         "private-message",
     825             :         ComputeTimestamp(msg),
     826           0 :         "from", msg.from().resource(),
     827           0 :         "text", msg.body());
     828           0 : }
     829             : 
     830             : /**
     831             :  * Handle portions of messages containing custom stanza extensions.
     832             :  */
     833           0 : bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
     834             : {
     835             :     DbgXMPP("handleIq [" << tag_xml(iq) << "]");
     836             : 
     837           0 :     if (iq.subtype() == gloox::IQ::Result)
     838             :     {
     839           0 :         const GameListQuery* gq = iq.findExtension<GameListQuery>(EXTGAMELISTQUERY);
     840           0 :         const BoardListQuery* bq = iq.findExtension<BoardListQuery>(EXTBOARDLISTQUERY);
     841           0 :         const ProfileQuery* pq = iq.findExtension<ProfileQuery>(EXTPROFILEQUERY);
     842           0 :         const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
     843           0 :         if (cd)
     844             :         {
     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           0 :                 LOGMESSAGE("XmppClient: Received connection data from invalid host: %s", iq.from().username());
     850           0 :                 return true;
     851             :             }
     852             : 
     853           0 :             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             :             }
     857             : 
     858           0 :             if (!cd->m_Error.empty())
     859             :             {
     860           0 :                 g_NetClient->HandleGetServerDataFailed(cd->m_Error.c_str());
     861           0 :                 return true;
     862             :             }
     863             : 
     864           0 :             g_NetClient->SetupServerData(cd->m_Ip.to_string(), stoi(cd->m_Port.to_string()), !cd->m_UseSTUN.empty());
     865           0 :             g_NetClient->TryToConnect(iq.from().full(), !cd->m_IsLocalIP.empty());
     866             :         }
     867           0 :         if (gq)
     868             :         {
     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           0 :                     LOGWARNING("XmppClient: Received empty game list in response to Game Register");
     874           0 :                     return true;
     875             :                 }
     876           0 :                 std::string publicIP = gq->m_GameList.front()->findAttribute("ip").to_string();
     877           0 :                 if (publicIP.empty())
     878             :                 {
     879           0 :                     LOGWARNING("XmppClient: Received game with no IP in response to Game Register");
     880           0 :                     return true;
     881             :                 }
     882           0 :                 g_NetServer->SetConnectionData(publicIP, g_NetServer->GetPublicPort());
     883           0 :                 return true;
     884             :             }
     885             : 
     886           0 :             for (const glooxwrapper::Tag* const& t : m_GameList)
     887           0 :                 glooxwrapper::Tag::free(t);
     888           0 :             m_GameList.clear();
     889             : 
     890           0 :             for (const glooxwrapper::Tag* const& t : gq->m_GameList)
     891           0 :                 m_GameList.emplace_back(t->clone());
     892             : 
     893           0 :             CreateGUIMessage("game", "gamelist", std::time(nullptr));
     894             :         }
     895           0 :         if (bq)
     896             :         {
     897           0 :             if (bq->m_Command == "boardlist")
     898             :             {
     899           0 :                 for (const glooxwrapper::Tag* const& t : m_BoardList)
     900           0 :                     glooxwrapper::Tag::free(t);
     901           0 :                 m_BoardList.clear();
     902             : 
     903           0 :                 for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
     904           0 :                     m_BoardList.emplace_back(t->clone());
     905             : 
     906           0 :                 CreateGUIMessage("game", "leaderboard", std::time(nullptr));
     907             :             }
     908           0 :             else if (bq->m_Command == "ratinglist")
     909             :             {
     910           0 :                 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             :                     {
     915           0 :                         it->second.m_Rating = t->findAttribute("rating");
     916           0 :                         m_PlayerMapUpdate = true;
     917             :                     }
     918             :                 }
     919           0 :                 CreateGUIMessage("game", "ratinglist", std::time(nullptr));
     920             :             }
     921             :         }
     922           0 :         if (pq)
     923             :         {
     924           0 :             for (const glooxwrapper::Tag* const& t : m_Profile)
     925           0 :                 glooxwrapper::Tag::free(t);
     926           0 :             m_Profile.clear();
     927             : 
     928           0 :             for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile)
     929           0 :                 m_Profile.emplace_back(t->clone());
     930             : 
     931           0 :             CreateGUIMessage("game", "profile", std::time(nullptr));
     932             :         }
     933             :     }
     934           0 :     else if (iq.subtype() == gloox::IQ::Set)
     935             :     {
     936           0 :         const LobbyAuth* lobbyAuth = iq.findExtension<LobbyAuth>(EXTLOBBYAUTH);
     937           0 :         if (lobbyAuth)
     938             :         {
     939           0 :             LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username());
     940             : 
     941           0 :             glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     942           0 :             m_client->send(response);
     943             : 
     944           0 :             if (g_NetServer)
     945           0 :                 g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string());
     946             :             else
     947           0 :                 LOGMESSAGE("Received lobby authentication request, but not hosting currently!");
     948             :         }
     949             :     }
     950           0 :     else if (iq.subtype() == gloox::IQ::Get)
     951             :     {
     952           0 :         const ConnectionData* cd = iq.findExtension<ConnectionData>(EXTCONNECTIONDATA);
     953           0 :         if (cd)
     954             :         {
     955           0 :             LOGMESSAGE("XmppClient: Received request for connection data from %s", iq.from().username());
     956           0 :             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             : 
     962           0 :                 response.addExtension(connectionData);
     963             : 
     964           0 :                 m_client->send(response);
     965           0 :                 return true;
     966             :             }
     967           0 :             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             : 
     973           0 :                 response.addExtension(connectionData);
     974             : 
     975           0 :                 m_client->send(response);
     976           0 :                 return true;
     977             :             }
     978           0 :             if (!g_NetServer->CheckPasswordAndIncrement(iq.from().username(), cd->m_Password.to_string(), cd->m_ClientSalt.to_string()))
     979             :             {
     980           0 :                 glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     981           0 :                 ConnectionData* connectionData = new ConnectionData();
     982           0 :                 connectionData->m_Error = "invalid_password";
     983             : 
     984           0 :                 response.addExtension(connectionData);
     985             : 
     986           0 :                 m_client->send(response);
     987           0 :                 return true;
     988             :             }
     989             : 
     990           0 :             glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
     991           0 :             ConnectionData* connectionData = new ConnectionData();
     992             : 
     993           0 :             if (cd->m_IsLocalIP.to_string() == "0")
     994             :             {
     995           0 :                 connectionData->m_Ip = g_NetServer->GetPublicIp();
     996           0 :                 connectionData->m_Port = std::to_string(g_NetServer->GetPublicPort());
     997           0 :                 connectionData->m_UseSTUN = g_NetServer->GetUseSTUN() ? "true" : "";
     998           0 :                 connectionData->m_IsLocalIP = "";
     999             :             }
    1000             :             else
    1001             :             {
    1002           0 :                 CStr ip;
    1003           0 :                 if (StunClient::FindLocalIP(ip))
    1004             :                 {
    1005           0 :                     connectionData->m_Ip = ip;
    1006           0 :                     connectionData->m_Port = std::to_string(g_NetServer->GetLocalPort());
    1007           0 :                     connectionData->m_UseSTUN = "";
    1008           0 :                     connectionData->m_IsLocalIP = "true";
    1009             :                 }
    1010             :                 else
    1011           0 :                     connectionData->m_Error = "local_ip_failed";
    1012             :             }
    1013             : 
    1014           0 :             response.addExtension(connectionData);
    1015             : 
    1016           0 :             m_client->send(response);
    1017             :         }
    1018             : 
    1019             :     }
    1020           0 :     else if (iq.subtype() == gloox::IQ::Error)
    1021           0 :         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           0 :         LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str());
    1026             :     }
    1027             : 
    1028           0 :     return true;
    1029             : }
    1030             : 
    1031             : /**
    1032             :  * Update local data when a user changes presence.
    1033             :  */
    1034           0 : void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom& UNUSED(room), const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
    1035             : {
    1036           0 :     const glooxwrapper::string& nick = participant.nick->resource();
    1037             : 
    1038           0 :     if (presence.presence() == gloox::Presence::Unavailable)
    1039             :     {
    1040           0 :         if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
    1041             :         {
    1042             :             // we have a nick change
    1043           0 :             if (m_PlayerMap.find(participant.newNick) == m_PlayerMap.end())
    1044           0 :                 m_PlayerMap.emplace(
    1045             :                     std::piecewise_construct,
    1046           0 :                     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           0 :                 LOGERROR("Nickname changed to an existing nick!");
    1050             : 
    1051             :             DbgXMPP(nick << " is now known as " << participant.newNick);
    1052           0 :             CreateGUIMessage(
    1053             :                 "chat",
    1054             :                 "nick",
    1055             :                 std::time(nullptr),
    1056             :                 "oldnick", nick,
    1057             :                 "newnick", participant.newNick);
    1058             :         }
    1059           0 :         else if (participant.flags & gloox::UserKicked)
    1060             :         {
    1061             :             DbgXMPP(nick << " was kicked. Reason: " << participant.reason);
    1062           0 :             CreateGUIMessage(
    1063             :                 "chat",
    1064             :                 "kicked",
    1065             :                 std::time(nullptr),
    1066             :                 "nick", nick,
    1067             :                 "reason", participant.reason);
    1068             :         }
    1069           0 :         else if (participant.flags & gloox::UserBanned)
    1070             :         {
    1071             :             DbgXMPP(nick << " was banned. Reason: " << participant.reason);
    1072           0 :             CreateGUIMessage(
    1073             :                 "chat",
    1074             :                 "banned",
    1075             :                 std::time(nullptr),
    1076             :                 "nick", nick,
    1077             :                 "reason", participant.reason);
    1078             :         }
    1079             :         else
    1080             :         {
    1081             :             DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
    1082           0 :             CreateGUIMessage(
    1083             :                 "chat",
    1084             :                 "leave",
    1085             :                 std::time(nullptr),
    1086             :                 "nick", nick);
    1087             :         }
    1088           0 :         m_PlayerMap.erase(nick);
    1089             :     }
    1090             :     else
    1091             :     {
    1092           0 :         const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1093             : 
    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             :          * out. We will always be the last to join during initialization.
    1097             :          */
    1098           0 :         if (!m_initialLoadComplete)
    1099             :         {
    1100           0 :             if (m_mucRoom->nick() == nick)
    1101           0 :                 m_initialLoadComplete = true;
    1102             :         }
    1103           0 :         else if (it == m_PlayerMap.end())
    1104             :         {
    1105           0 :             CreateGUIMessage(
    1106             :                 "chat",
    1107             :                 "join",
    1108             :                 std::time(nullptr),
    1109             :                 "nick", nick);
    1110             :         }
    1111           0 :         else if (it->second.m_Role != participant.role)
    1112             :         {
    1113           0 :             CreateGUIMessage(
    1114             :                 "chat",
    1115             :                 "role",
    1116             :                 std::time(nullptr),
    1117             :                 "nick", nick,
    1118           0 :                 "oldrole", it->second.m_Role,
    1119             :                 "newrole", participant.role);
    1120             :         }
    1121             :         else
    1122             :         {
    1123             :             // Don't create a GUI message for regular presence changes, because
    1124             :             // 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             :         }
    1127             : 
    1128             :         DbgXMPP(
    1129             :             nick << " is in the room, "
    1130             :             "presence: " << GetPresenceString(presence.presence()) << ", "
    1131             :             "role: "<< GetRoleString(participant.role));
    1132             : 
    1133           0 :         if (it == m_PlayerMap.end())
    1134             :         {
    1135           0 :             m_PlayerMap.emplace(
    1136             :                 std::piecewise_construct,
    1137           0 :                 std::forward_as_tuple(nick),
    1138           0 :                 std::forward_as_tuple(presence.presence(), participant.role, std::string()));
    1139             :         }
    1140             :         else
    1141             :         {
    1142           0 :             it->second.m_Presence = presence.presence();
    1143           0 :             it->second.m_Role = participant.role;
    1144             :         }
    1145             :     }
    1146             : 
    1147           0 :     m_PlayerMapUpdate = true;
    1148           0 : }
    1149             : 
    1150             : /**
    1151             :  * Update local cache when subject changes.
    1152             :  */
    1153           0 : 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           0 :     CreateGUIMessage(
    1158             :         "chat",
    1159             :         "subject",
    1160             :         std::time(nullptr),
    1161             :         "nick", nick,
    1162             :         "subject", m_Subject);
    1163           0 : }
    1164             : 
    1165             : /**
    1166             :  * Get current subject.
    1167             :  */
    1168           0 : const std::wstring& XmppClient::GetSubject()
    1169             : {
    1170           0 :     return m_Subject;
    1171             : }
    1172             : 
    1173             : /**
    1174             :  * Request nick change, real change via mucRoomHandler.
    1175             :  *
    1176             :  * @param nick Desired nickname
    1177             :  */
    1178           0 : void XmppClient::SetNick(const std::string& nick)
    1179             : {
    1180           0 :     m_mucRoom->setNick(nick);
    1181           0 : }
    1182             : 
    1183             : /**
    1184             :  * Get current nickname.
    1185             :  */
    1186           0 : std::string XmppClient::GetNick() const
    1187             : {
    1188           0 :     return m_mucRoom->nick().to_string();
    1189             : }
    1190             : 
    1191           0 : std::string XmppClient::GetJID() const
    1192             : {
    1193           0 :     return m_client->getJID().to_string();
    1194             : }
    1195             : 
    1196             : /**
    1197             :  * Kick a player from the current room.
    1198             :  *
    1199             :  * @param nick Nickname to be kicked
    1200             :  * @param reason Reason the player was kicked
    1201             :  */
    1202           0 : void XmppClient::kick(const std::string& nick, const std::string& reason)
    1203             : {
    1204           0 :     m_mucRoom->kick(nick, reason);
    1205           0 : }
    1206             : 
    1207             : /**
    1208             :  * Ban a player from the current room.
    1209             :  *
    1210             :  * @param nick Nickname to be banned
    1211             :  * @param reason Reason the player was banned
    1212             :  */
    1213           0 : void XmppClient::ban(const std::string& nick, const std::string& reason)
    1214             : {
    1215           0 :     m_mucRoom->ban(nick, reason);
    1216           0 : }
    1217             : 
    1218             : /**
    1219             :  * Change the xmpp presence of the client.
    1220             :  *
    1221             :  * @param presence A string containing the desired presence
    1222             :  */
    1223           0 : void XmppClient::SetPresence(const std::string& presence)
    1224             : {
    1225             : #define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
    1226           0 :     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             :     // The others are not to be set
    1232             : #undef IF
    1233           0 :     else LOGERROR("Unknown presence '%s'", presence.c_str());
    1234           0 : }
    1235             : 
    1236             : /**
    1237             :  * Get the current xmpp presence of the given nick.
    1238             :  */
    1239           0 : const char* XmppClient::GetPresence(const std::string& nick)
    1240             : {
    1241           0 :     const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1242             : 
    1243           0 :     if (it == m_PlayerMap.end())
    1244           0 :         return "offline";
    1245             : 
    1246           0 :     return GetPresenceString(it->second.m_Presence);
    1247             : }
    1248             : 
    1249             : /**
    1250             :  * Get the current xmpp role of the given nick.
    1251             :  */
    1252           0 : const char* XmppClient::GetRole(const std::string& nick)
    1253             : {
    1254           0 :     const PlayerMap::iterator it = m_PlayerMap.find(nick);
    1255             : 
    1256           0 :     if (it == m_PlayerMap.end())
    1257           0 :         return "";
    1258             : 
    1259           0 :     return GetRoleString(it->second.m_Role);
    1260             : }
    1261             : 
    1262             : /**
    1263             :  * Get the most recent received rating of the given nick.
    1264             :  * Notice that this doesn't request a rating profile if it hasn't been received yet.
    1265             :  */
    1266           0 : std::wstring XmppClient::GetRating(const std::string& nick)
    1267             : {
    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           0 :     return wstring_from_utf8(it->second.m_Rating.to_string());
    1274             : }
    1275             : 
    1276             : /*****************************************************
    1277             :  * Utilities                                         *
    1278             :  *****************************************************/
    1279             : 
    1280             : /**
    1281             :  * 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             :  * Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
    1284             :  *
    1285             :  * @returns Seconds since the epoch.
    1286             :  */
    1287           0 : std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg)
    1288             : {
    1289             :     // Only historic messages contain a timestamp!
    1290           0 :     if (!msg.when())
    1291           0 :         return std::time(nullptr);
    1292             : 
    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           0 :         if (dateTime)
    1298           0 :             return dateTime / 1000.0;
    1299             :     }
    1300             : 
    1301           0 :     return std::time(nullptr);
    1302             : }
    1303             : 
    1304             : /**
    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           0 :     switch (presenceType)
    1310             :     {
    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           0 :     CASE(DND, "playing");
    1316           0 :     CASE(XA, "away");
    1317           0 :     CASE(Unavailable, "offline");
    1318           0 :     CASE(Probe, "probe");
    1319           0 :     CASE(Error, "error");
    1320           0 :     CASE(Invalid, "invalid");
    1321           0 :     default:
    1322           0 :         LOGERROR("Unknown presence type '%d'", static_cast<int>(presenceType));
    1323           0 :         return "";
    1324             : #undef CASE
    1325             :     }
    1326             : }
    1327             : 
    1328             : /**
    1329             :  * Convert a gloox role type to an untranslated string literal to be used as an identifier by the scripts.
    1330             :  */
    1331           0 : const char* XmppClient::GetRoleString(const gloox::MUCRoomRole role)
    1332             : {
    1333           0 :     switch (role)
    1334             :     {
    1335             : #define CASE(X, Y) case gloox::X: return Y
    1336           0 :     CASE(RoleNone, "none");
    1337           0 :     CASE(RoleVisitor, "visitor");
    1338           0 :     CASE(RoleParticipant, "participant");
    1339           0 :     CASE(RoleModerator, "moderator");
    1340           0 :     CASE(RoleInvalid, "invalid");
    1341           0 :     default:
    1342           0 :         LOGERROR("Unknown role type '%d'", static_cast<int>(role));
    1343           0 :         return "";
    1344             : #undef CASE
    1345             :     }
    1346             : }
    1347             : 
    1348             : /**
    1349             :  * Translates a gloox certificate error codes, i.e. gloox certificate statuses except CertOk.
    1350             :  * Keep in sync with specifications.
    1351             :  */
    1352           0 : std::string XmppClient::CertificateErrorToString(gloox::CertStatus status)
    1353             : {
    1354             :     std::map<gloox::CertStatus, std::string> certificateErrorStrings = {
    1355           0 :         { gloox::CertInvalid, g_L10n.Translate("The certificate is not trusted.") },
    1356           0 :         { 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           0 :         { 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           0 :     };
    1363             : 
    1364           0 :     std::string result;
    1365             : 
    1366           0 :     for (std::map<gloox::CertStatus, std::string>::iterator it = certificateErrorStrings.begin(); it != certificateErrorStrings.end(); ++it)
    1367           0 :         if (status & it->first)
    1368           0 :             result += "\n" + it->second;
    1369             : 
    1370           0 :     return result;
    1371             : }
    1372             : 
    1373             : /**
    1374             :  * Convert a gloox stanza error type to string.
    1375             :  * Keep in sync with Gloox documentation
    1376             :  *
    1377             :  * @param err Error to be converted
    1378             :  * @return Converted error string
    1379             :  */
    1380           0 : std::string XmppClient::StanzaErrorToString(gloox::StanzaError err)
    1381             : {
    1382             : #define CASE(X, Y) case gloox::X: return Y
    1383             : #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
    1384           0 :     switch (err)
    1385             :     {
    1386           0 :     CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
    1387           0 :     DEBUG_CASE(StanzaErrorBadRequest, "Server received malformed XML");
    1388           0 :     CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in"));
    1389           0 :     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           0 :     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           0 :     CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized"));
    1398           0 :     DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request");
    1399           0 :     DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment");
    1400           0 :     CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable"));
    1401           0 :     DEBUG_CASE(StanzaErrorRedirect, "Request redirected");
    1402           0 :     CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required"));
    1403           0 :     DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
    1404           0 :     DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out");
    1405           0 :     DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints");
    1406           0 :     CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable"));
    1407           0 :     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           0 :     default:
    1411           0 :         return g_L10n.Translate("Unknown error");
    1412             :     }
    1413             : #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             :  * @return Converted error string
    1423             :  */
    1424           0 : std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err)
    1425             : {
    1426             : #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           0 :     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           0 :     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           0 :     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           0 :     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           0 :     CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory"));
    1442           0 :     DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
    1443           0 :     CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
    1444           0 :     CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption"));
    1445           0 :     DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed");
    1446           0 :     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           0 :     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             : 
    1456             : /**
    1457             :  * Convert a gloox registration result enum to string
    1458             :  * Keep in sync with Gloox documentation
    1459             :  *
    1460             :  * @param err Enum to be converted
    1461             :  * @return Converted string
    1462             :  */
    1463           0 : std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res)
    1464             : {
    1465             : #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           0 :     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           0 :     CASE(RegistrationConflict, g_L10n.Translate("Username already exists"));
    1472           0 :     DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change");
    1473           0 :     DEBUG_CASE(RegistrationBadRequest, "Server received an incomplete request");
    1474           0 :     DEBUG_CASE(RegistrationForbidden, "Registration forbidden");
    1475           0 :     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           0 :     DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes");
    1478           0 :     default:
    1479           0 :         return "";
    1480             :     }
    1481             : #undef DEBUG_CASE
    1482             : #undef CASE
    1483             : }
    1484             : 
    1485           0 : void XmppClient::SendStunEndpointToHost(const std::string& ip, u16 port, const std::string& hostJIDStr)
    1486             : {
    1487             :     DbgXMPP("SendStunEndpointToHost " << hostJIDStr);
    1488             : 
    1489           0 :     glooxwrapper::JID hostJID(hostJIDStr);
    1490           0 :     glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
    1491           0 :     session.sessionInitiate(ip.c_str(), port);
    1492           0 : }
    1493             : 
    1494           0 : void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle)
    1495             : {
    1496           0 :     if (action == gloox::Jingle::SessionInitiate)
    1497           0 :         handleSessionInitiation(session, jingle);
    1498           0 : }
    1499             : 
    1500           0 : void XmppClient::handleSessionInitiation(glooxwrapper::Jingle::Session& UNUSED(session), const glooxwrapper::Jingle::Session::Jingle& jingle)
    1501             : {
    1502           0 :     glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle.getCandidate();
    1503             : 
    1504           0 :     if (candidate.ip.empty())
    1505             :     {
    1506           0 :         LOGERROR("Failed to retrieve Jingle candidate");
    1507           0 :         return;
    1508             :     }
    1509             : 
    1510           0 :     if (!g_NetServer)
    1511             :     {
    1512           0 :         LOGERROR("Received STUN connection request, but not hosting currently!");
    1513           0 :         return;
    1514             :     }
    1515             : 
    1516           0 :     g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
    1517           3 : }

Generated by: LCOV version 1.13