mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-08 09:03:57 -07:00
Turn Card, Deck_List, Protocol, RNG, Network (Client, Server), Settings and Utility into libraries and remove cockatrice_common. (#6212)
--------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: ebbit1q <ebbit1q@gmail.com>
This commit is contained in:
parent
be1403c920
commit
1ef07309d6
605 changed files with 3812 additions and 3408 deletions
|
|
@ -0,0 +1,16 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
add_subdirectory(abstract)
|
||||
add_subdirectory(local)
|
||||
add_subdirectory(remote)
|
||||
|
||||
add_library(libcockatrice_network_client INTERFACE)
|
||||
|
||||
target_include_directories(libcockatrice_network_client INTERFACE .)
|
||||
|
||||
target_link_libraries(
|
||||
libcockatrice_network_client INTERFACE ${COCKATRICE_QT_MODULES} libcockatrice_network_client_abstract
|
||||
libcockatrice_network_client_local libcockatrice_network_client_remote
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS abstract_client.h)
|
||||
|
||||
set(SOURCES abstract_client.cpp)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(libcockatrice_network_client_abstract STATIC ${MOC_SOURCES} ${SOURCES})
|
||||
|
||||
add_dependencies(libcockatrice_network_client_abstract libcockatrice_protocol libcockatrice_network_server_remote)
|
||||
|
||||
target_include_directories(libcockatrice_network_client_abstract PUBLIC .)
|
||||
|
||||
target_link_libraries(
|
||||
libcockatrice_network_client_abstract PUBLIC ${COCKATRICE_QT_MODULES} libcockatrice_protocol
|
||||
libcockatrice_network_server_remote
|
||||
)
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#include "abstract_client.h"
|
||||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include <libcockatrice/protocol/featureset.h>
|
||||
#include <libcockatrice/protocol/get_pb_extension.h>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_add_to_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_list_rooms.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_notify_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_remove_from_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_identification.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_user_joined.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_user_left.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/server_message.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
AbstractClient::AbstractClient(QObject *parent)
|
||||
: QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false)
|
||||
{
|
||||
qRegisterMetaType<QVariant>("QVariant");
|
||||
qRegisterMetaType<CommandContainer>("CommandContainer");
|
||||
qRegisterMetaType<Response>("Response");
|
||||
qRegisterMetaType<Response::ResponseCode>("Response::ResponseCode");
|
||||
qRegisterMetaType<ClientStatus>("ClientStatus");
|
||||
qRegisterMetaType<RoomEvent>("RoomEvent");
|
||||
qRegisterMetaType<GameEventContainer>("GameEventContainer");
|
||||
qRegisterMetaType<Event_ServerIdentification>("Event_ServerIdentification");
|
||||
qRegisterMetaType<Event_ConnectionClosed>("Event_ConnectionClosed");
|
||||
qRegisterMetaType<Event_ServerShutdown>("Event_ServerShutdown");
|
||||
qRegisterMetaType<Event_AddToList>("Event_AddToList");
|
||||
qRegisterMetaType<Event_RemoveFromList>("Event_RemoveFromList");
|
||||
qRegisterMetaType<Event_UserJoined>("Event_UserJoined");
|
||||
qRegisterMetaType<Event_UserLeft>("Event_UserLeft");
|
||||
qRegisterMetaType<Event_ServerMessage>("Event_ServerMessage");
|
||||
qRegisterMetaType<Event_ListRooms>("Event_ListRooms");
|
||||
qRegisterMetaType<Event_GameJoined>("Event_GameJoined");
|
||||
qRegisterMetaType<Event_UserMessage>("Event_UserMessage");
|
||||
qRegisterMetaType<Event_NotifyUser>("Event_NotifyUser");
|
||||
qRegisterMetaType<ServerInfo_User>("ServerInfo_User");
|
||||
qRegisterMetaType<QList<ServerInfo_User>>("QList<ServerInfo_User>");
|
||||
qRegisterMetaType<Event_ReplayAdded>("Event_ReplayAdded");
|
||||
qRegisterMetaType<QList<QString>>("missingFeatures");
|
||||
qRegisterMetaType<PendingCommand *>("pendingCommand");
|
||||
|
||||
FeatureSet features;
|
||||
features.initalizeFeatureList(clientFeatures);
|
||||
|
||||
connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand);
|
||||
}
|
||||
|
||||
AbstractClient::~AbstractClient()
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractClient::processProtocolItem(const ServerMessage &item)
|
||||
{
|
||||
switch (item.message_type()) {
|
||||
case ServerMessage::RESPONSE: {
|
||||
const Response &response = item.response();
|
||||
const int cmdId = response.cmd_id();
|
||||
|
||||
PendingCommand *pend = pendingCommands.value(cmdId, 0);
|
||||
if (!pend)
|
||||
return;
|
||||
pendingCommands.remove(cmdId);
|
||||
|
||||
pend->processResponse(response);
|
||||
pend->deleteLater();
|
||||
break;
|
||||
}
|
||||
case ServerMessage::SESSION_EVENT: {
|
||||
const SessionEvent &event = item.session_event();
|
||||
switch ((SessionEvent::SessionEventType)getPbExtension(event)) {
|
||||
case SessionEvent::SERVER_IDENTIFICATION:
|
||||
emit serverIdentificationEventReceived(event.GetExtension(Event_ServerIdentification::ext));
|
||||
break;
|
||||
case SessionEvent::SERVER_MESSAGE:
|
||||
emit serverMessageEventReceived(event.GetExtension(Event_ServerMessage::ext));
|
||||
break;
|
||||
case SessionEvent::SERVER_SHUTDOWN:
|
||||
emit serverShutdownEventReceived(event.GetExtension(Event_ServerShutdown::ext));
|
||||
break;
|
||||
case SessionEvent::CONNECTION_CLOSED:
|
||||
emit connectionClosedEventReceived(event.GetExtension(Event_ConnectionClosed::ext));
|
||||
break;
|
||||
case SessionEvent::USER_MESSAGE:
|
||||
emit userMessageEventReceived(event.GetExtension(Event_UserMessage::ext));
|
||||
break;
|
||||
case SessionEvent::NOTIFY_USER:
|
||||
emit notifyUserEventReceived(event.GetExtension(Event_NotifyUser::ext));
|
||||
break;
|
||||
case SessionEvent::LIST_ROOMS:
|
||||
emit listRoomsEventReceived(event.GetExtension(Event_ListRooms::ext));
|
||||
break;
|
||||
case SessionEvent::ADD_TO_LIST:
|
||||
emit addToListEventReceived(event.GetExtension(Event_AddToList::ext));
|
||||
break;
|
||||
case SessionEvent::REMOVE_FROM_LIST:
|
||||
emit removeFromListEventReceived(event.GetExtension(Event_RemoveFromList::ext));
|
||||
break;
|
||||
case SessionEvent::USER_JOINED:
|
||||
emit userJoinedEventReceived(event.GetExtension(Event_UserJoined::ext));
|
||||
break;
|
||||
case SessionEvent::USER_LEFT:
|
||||
emit userLeftEventReceived(event.GetExtension(Event_UserLeft::ext));
|
||||
break;
|
||||
case SessionEvent::GAME_JOINED:
|
||||
emit gameJoinedEventReceived(event.GetExtension(Event_GameJoined::ext));
|
||||
break;
|
||||
case SessionEvent::REPLAY_ADDED:
|
||||
emit replayAddedEventReceived(event.GetExtension(Event_ReplayAdded::ext));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerMessage::GAME_EVENT_CONTAINER: {
|
||||
emit gameEventContainerReceived(item.game_event_container());
|
||||
break;
|
||||
}
|
||||
case ServerMessage::ROOM_EVENT: {
|
||||
emit roomEventReceived(item.room_event());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractClient::setStatus(const ClientStatus _status)
|
||||
{
|
||||
QMutexLocker locker(&clientMutex);
|
||||
if (_status != status) {
|
||||
status = _status;
|
||||
emit statusChanged(_status);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractClient::sendCommand(const CommandContainer &cont)
|
||||
{
|
||||
sendCommand(new PendingCommand(cont));
|
||||
}
|
||||
|
||||
void AbstractClient::sendCommand(PendingCommand *pend)
|
||||
{
|
||||
pend->moveToThread(thread());
|
||||
emit sigQueuePendingCommand(pend);
|
||||
}
|
||||
|
||||
void AbstractClient::queuePendingCommand(PendingCommand *pend)
|
||||
{
|
||||
// This function is always called from the client thread via signal/slot.
|
||||
const int cmdId = getNewCmdId();
|
||||
pend->getCommandContainer().set_cmd_id(cmdId);
|
||||
|
||||
pendingCommands.insert(cmdId, pend);
|
||||
|
||||
sendCommandContainer(pend->getCommandContainer());
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareSessionCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
SessionCommand *c = cont.add_session_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId)
|
||||
{
|
||||
CommandContainer cont;
|
||||
RoomCommand *c = cont.add_room_command();
|
||||
cont.set_room_id(roomId);
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareModeratorCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
ModeratorCommand *c = cont.add_moderator_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareAdminCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
AdminCommand *c = cont.add_admin_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* @file abstract_client.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef ABSTRACTCLIENT_H
|
||||
#define ABSTRACTCLIENT_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
|
||||
class PendingCommand;
|
||||
class CommandContainer;
|
||||
class RoomEvent;
|
||||
class GameEventContainer;
|
||||
class ServerMessage;
|
||||
class Event_ServerIdentification;
|
||||
class Event_AddToList;
|
||||
class Event_RemoveFromList;
|
||||
class Event_UserJoined;
|
||||
class Event_UserLeft;
|
||||
class Event_ServerMessage;
|
||||
class Event_ListRooms;
|
||||
class Event_GameJoined;
|
||||
class Event_UserMessage;
|
||||
class Event_NotifyUser;
|
||||
class Event_ConnectionClosed;
|
||||
class Event_ServerShutdown;
|
||||
class Event_ReplayAdded;
|
||||
class FeatureSet;
|
||||
|
||||
enum ClientStatus
|
||||
{
|
||||
StatusDisconnected,
|
||||
StatusDisconnecting,
|
||||
StatusConnecting,
|
||||
StatusRegistering,
|
||||
StatusActivating,
|
||||
StatusLoggingIn,
|
||||
StatusLoggedIn,
|
||||
StatusRequestingForgotPassword,
|
||||
StatusSubmitForgotPasswordReset,
|
||||
StatusSubmitForgotPasswordChallenge,
|
||||
StatusGettingPasswordSalt,
|
||||
};
|
||||
|
||||
class AbstractClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void statusChanged(ClientStatus _status);
|
||||
void maxPingTime(int seconds, int maxSeconds);
|
||||
|
||||
// Room events
|
||||
void roomEventReceived(const RoomEvent &event);
|
||||
// Game events
|
||||
void gameEventContainerReceived(const GameEventContainer &event);
|
||||
// Session events
|
||||
void serverIdentificationEventReceived(const Event_ServerIdentification &event);
|
||||
void connectionClosedEventReceived(const Event_ConnectionClosed &event);
|
||||
void serverShutdownEventReceived(const Event_ServerShutdown &event);
|
||||
void addToListEventReceived(const Event_AddToList &event);
|
||||
void removeFromListEventReceived(const Event_RemoveFromList &event);
|
||||
void userJoinedEventReceived(const Event_UserJoined &event);
|
||||
void userLeftEventReceived(const Event_UserLeft &event);
|
||||
void serverMessageEventReceived(const Event_ServerMessage &event);
|
||||
void listRoomsEventReceived(const Event_ListRooms &event);
|
||||
void gameJoinedEventReceived(const Event_GameJoined &event);
|
||||
void userMessageEventReceived(const Event_UserMessage &event);
|
||||
void notifyUserEventReceived(const Event_NotifyUser &event);
|
||||
void userInfoChanged(const ServerInfo_User &userInfo);
|
||||
void buddyListReceived(const QList<ServerInfo_User> &buddyList);
|
||||
void ignoreListReceived(const QList<ServerInfo_User> &ignoreList);
|
||||
void replayAddedEventReceived(const Event_ReplayAdded &event);
|
||||
void registerAccepted();
|
||||
void registerAcceptedNeedsActivate();
|
||||
void activateAccepted();
|
||||
|
||||
void sigQueuePendingCommand(PendingCommand *pend);
|
||||
|
||||
private:
|
||||
int nextCmdId;
|
||||
mutable QMutex clientMutex;
|
||||
ClientStatus status;
|
||||
private slots:
|
||||
void queuePendingCommand(PendingCommand *pend);
|
||||
protected slots:
|
||||
void processProtocolItem(const ServerMessage &item);
|
||||
|
||||
protected:
|
||||
QMap<int, PendingCommand *> pendingCommands;
|
||||
QString userName, password, email, country, realName, token;
|
||||
bool serverSupportsPasswordHash;
|
||||
void setStatus(ClientStatus _status);
|
||||
int getNewCmdId()
|
||||
{
|
||||
return nextCmdId++;
|
||||
}
|
||||
virtual void sendCommandContainer(const CommandContainer &cont) = 0;
|
||||
|
||||
public:
|
||||
explicit AbstractClient(QObject *parent = nullptr);
|
||||
~AbstractClient() override;
|
||||
|
||||
ClientStatus getStatus() const
|
||||
{
|
||||
QMutexLocker locker(&clientMutex);
|
||||
return status;
|
||||
}
|
||||
void sendCommand(const CommandContainer &cont);
|
||||
void sendCommand(PendingCommand *pend);
|
||||
|
||||
bool getServerSupportsPasswordHash() const
|
||||
{
|
||||
return serverSupportsPasswordHash;
|
||||
}
|
||||
const QString &getUserName() const
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
static PendingCommand *prepareSessionCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
|
||||
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd);
|
||||
|
||||
QMap<QString, bool> clientFeatures;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS local_client.h)
|
||||
|
||||
set(SOURCES local_client.cpp)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(libcockatrice_network_client_local STATIC ${MOC_SOURCES} ${SOURCES})
|
||||
|
||||
add_dependencies(libcockatrice_network_client_local libcockatrice_network_client_abstract)
|
||||
|
||||
target_include_directories(libcockatrice_network_client_local PUBLIC .)
|
||||
|
||||
target_link_libraries(
|
||||
libcockatrice_network_client_local PUBLIC ${COCKATRICE_QT_MODULES} libcockatrice_network_client_abstract
|
||||
)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#include "local_client.h"
|
||||
|
||||
#include "../../server/local/local_server_interface.h"
|
||||
|
||||
#include <libcockatrice/protocol/debug_pb_message.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
|
||||
LocalClient::LocalClient(LocalServerInterface *_lsi,
|
||||
const QString &_playerName,
|
||||
const QString &_clientId,
|
||||
QObject *parent)
|
||||
: AbstractClient(parent), lsi(_lsi)
|
||||
{
|
||||
connect(lsi, &LocalServerInterface::itemToClient, this, &LocalClient::itemFromServer);
|
||||
|
||||
userName = _playerName;
|
||||
|
||||
Command_Login loginCmd;
|
||||
loginCmd.set_user_name(_playerName.toStdString());
|
||||
loginCmd.set_clientid(_clientId.toStdString());
|
||||
sendCommand(prepareSessionCommand(loginCmd));
|
||||
|
||||
Command_JoinRoom joinCmd;
|
||||
joinCmd.set_room_id(0);
|
||||
sendCommand(prepareSessionCommand(joinCmd));
|
||||
}
|
||||
|
||||
LocalClient::~LocalClient()
|
||||
{
|
||||
}
|
||||
|
||||
void LocalClient::sendCommandContainer(const CommandContainer &cont)
|
||||
{
|
||||
qCDebug(LocalClientLog).noquote() << userName << "OUT" << getSafeDebugString(cont);
|
||||
|
||||
lsi->itemFromClient(cont);
|
||||
}
|
||||
|
||||
void LocalClient::itemFromServer(const ServerMessage &item)
|
||||
{
|
||||
qCDebug(LocalClientLog).noquote() << userName << "IN" << getSafeDebugString(item);
|
||||
|
||||
processProtocolItem(item);
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file local_client.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef LOCALCLIENT_H
|
||||
#define LOCALCLIENT_H
|
||||
|
||||
#include "../abstract/abstract_client.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(LocalClientLog, "local_client");
|
||||
|
||||
class LocalServerInterface;
|
||||
|
||||
class LocalClient : public AbstractClient
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
LocalServerInterface *lsi;
|
||||
|
||||
public:
|
||||
LocalClient(LocalServerInterface *_lsi,
|
||||
const QString &_playerName,
|
||||
const QString &_clientId,
|
||||
QObject *parent = nullptr);
|
||||
~LocalClient() override;
|
||||
|
||||
void sendCommandContainer(const CommandContainer &cont) override;
|
||||
private slots:
|
||||
void itemFromServer(const ServerMessage &item);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS remote_client.h)
|
||||
|
||||
set(SOURCES remote_client.cpp)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(libcockatrice_network_client_remote STATIC ${MOC_SOURCES} ${SOURCES})
|
||||
|
||||
add_dependencies(libcockatrice_network_client_remote libcockatrice_network_client_abstract libcockatrice_protocol)
|
||||
|
||||
target_include_directories(libcockatrice_network_client_remote PUBLIC .)
|
||||
|
||||
target_link_libraries(
|
||||
libcockatrice_network_client_remote PUBLIC ${COCKATRICE_QT_MODULES} libcockatrice_network_client_abstract
|
||||
libcockatrice_settings libcockatrice_utility libcockatrice_protocol
|
||||
)
|
||||
|
|
@ -0,0 +1,730 @@
|
|||
#include "remote_client.h"
|
||||
|
||||
#include "../../../../cockatrice/src/main.h"
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
#include <QList>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QWebSocket>
|
||||
#include <libcockatrice/protocol/debug_pb_message.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_identification.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_activate.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_forgotpasswordrequest.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_login.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_password_salt.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_register.pb.h>
|
||||
#include <libcockatrice/protocol/pb/server_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
#include <libcockatrice/utility/passwordhasher.h>
|
||||
|
||||
static const unsigned int protocolVersion = 14;
|
||||
|
||||
RemoteClient::RemoteClient(QObject *parent)
|
||||
: AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false),
|
||||
usingWebSocket(false), messageLength(0), hashedPassword()
|
||||
{
|
||||
|
||||
clearNewClientFeatures();
|
||||
maxTimeout = SettingsCache::instance().getTimeOut();
|
||||
int keepalive = SettingsCache::instance().getKeepAlive();
|
||||
timer = new QTimer(this);
|
||||
timer->setInterval(keepalive * 1000);
|
||||
connect(timer, &QTimer::timeout, this, &RemoteClient::ping);
|
||||
|
||||
socket = new QTcpSocket(this);
|
||||
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
||||
connect(socket, &QTcpSocket::connected, this, &RemoteClient::slotConnected);
|
||||
connect(socket, &QTcpSocket::readyRead, this, &RemoteClient::readData);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
connect(socket, &QTcpSocket::errorOccurred, this, &RemoteClient::slotSocketError);
|
||||
#else
|
||||
connect(socket, qOverload<QAbstractSocket::SocketError>(&QTcpSocket::error), this, &RemoteClient::slotSocketError);
|
||||
#endif
|
||||
|
||||
websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
|
||||
connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived);
|
||||
connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
connect(websocket, &QWebSocket::errorOccurred, this, &RemoteClient::slotWebSocketError);
|
||||
#else
|
||||
connect(websocket, qOverload<QAbstractSocket::SocketError>(&QWebSocket::error), this,
|
||||
&RemoteClient::slotWebSocketError);
|
||||
#endif
|
||||
|
||||
connect(this, &RemoteClient::serverIdentificationEventReceived, this,
|
||||
&RemoteClient::processServerIdentificationEvent);
|
||||
connect(this, &RemoteClient::connectionClosedEventReceived, this, &RemoteClient::processConnectionClosedEvent);
|
||||
connect(this, &RemoteClient::sigConnectToServer, this, &RemoteClient::doConnectToServer);
|
||||
connect(this, &RemoteClient::sigDisconnectFromServer, this, &RemoteClient::doDisconnectFromServer);
|
||||
connect(this, &RemoteClient::sigRegisterToServer, this, &RemoteClient::doRegisterToServer);
|
||||
connect(this, &RemoteClient::sigActivateToServer, this, &RemoteClient::doActivateToServer);
|
||||
connect(this, &RemoteClient::sigRequestForgotPasswordToServer, this,
|
||||
&RemoteClient::doRequestForgotPasswordToServer);
|
||||
connect(this, &RemoteClient::sigSubmitForgotPasswordResetToServer, this,
|
||||
&RemoteClient::doSubmitForgotPasswordResetToServer);
|
||||
connect(this, &RemoteClient::sigSubmitForgotPasswordChallengeToServer, this,
|
||||
&RemoteClient::doSubmitForgotPasswordChallengeToServer);
|
||||
}
|
||||
|
||||
RemoteClient::~RemoteClient()
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
thread()->quit();
|
||||
}
|
||||
|
||||
void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/)
|
||||
{
|
||||
QString errorString = socket->errorString();
|
||||
doDisconnectFromServer();
|
||||
emit socketError(errorString);
|
||||
}
|
||||
|
||||
void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/)
|
||||
{
|
||||
|
||||
QString errorString = websocket->errorString();
|
||||
if (getStatus() != ClientStatus::StatusDisconnected) {
|
||||
doDisconnectFromServer();
|
||||
emit socketError(errorString);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::slotConnected()
|
||||
{
|
||||
timeRunning = lastDataReceived = 0;
|
||||
timer->start();
|
||||
|
||||
if (!usingWebSocket) {
|
||||
// dirty hack to be compatible with v14 server
|
||||
sendCommandContainer(CommandContainer());
|
||||
getNewCmdId();
|
||||
// end of hack
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event)
|
||||
{
|
||||
if (event.protocol_version() != protocolVersion) {
|
||||
emit protocolVersionMismatch(protocolVersion, event.protocol_version());
|
||||
setStatus(StatusDisconnecting);
|
||||
return;
|
||||
}
|
||||
serverSupportsPasswordHash = event.server_options() & Event_ServerIdentification::SupportsPasswordHash;
|
||||
|
||||
if (getStatus() == StatusRequestingForgotPassword) {
|
||||
Command_ForgotPasswordRequest cmdForgotPasswordRequest;
|
||||
cmdForgotPasswordRequest.set_user_name(userName.toStdString());
|
||||
cmdForgotPasswordRequest.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordRequest);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::requestForgotPasswordResponse);
|
||||
sendCommand(pend);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getStatus() == StatusSubmitForgotPasswordReset) {
|
||||
Command_ForgotPasswordReset cmdForgotPasswordReset;
|
||||
cmdForgotPasswordReset.set_user_name(userName.toStdString());
|
||||
cmdForgotPasswordReset.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
cmdForgotPasswordReset.set_token(token.toStdString());
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString());
|
||||
} else if (!password.isEmpty()) {
|
||||
qCWarning(RemoteClientLog) << "using plain text password to reset password";
|
||||
cmdForgotPasswordReset.set_new_password(password.toStdString());
|
||||
}
|
||||
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::submitForgotPasswordResetResponse);
|
||||
sendCommand(pend);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getStatus() == StatusSubmitForgotPasswordChallenge) {
|
||||
Command_ForgotPasswordChallenge cmdForgotPasswordChallenge;
|
||||
cmdForgotPasswordChallenge.set_user_name(userName.toStdString());
|
||||
cmdForgotPasswordChallenge.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
cmdForgotPasswordChallenge.set_email(email.toStdString());
|
||||
PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordChallenge);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::submitForgotPasswordChallengeResponse);
|
||||
sendCommand(pend);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getStatus() == StatusRegistering) {
|
||||
Command_Register cmdRegister;
|
||||
cmdRegister.set_user_name(userName.toStdString());
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
cmdRegister.set_hashed_password(hashedPassword.toStdString());
|
||||
} else if (!password.isEmpty()) {
|
||||
qCWarning(RemoteClientLog) << "using plain text password to register";
|
||||
cmdRegister.set_password(password.toStdString());
|
||||
}
|
||||
cmdRegister.set_email(email.toStdString());
|
||||
cmdRegister.set_country(country.toStdString());
|
||||
cmdRegister.set_real_name(realName.toStdString());
|
||||
cmdRegister.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
PendingCommand *pend = prepareSessionCommand(cmdRegister);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::registerResponse);
|
||||
sendCommand(pend);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (getStatus() == StatusActivating) {
|
||||
Command_Activate cmdActivate;
|
||||
cmdActivate.set_user_name(userName.toStdString());
|
||||
cmdActivate.set_token(token.toStdString());
|
||||
cmdActivate.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdActivate);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::activateResponse);
|
||||
sendCommand(pend);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
doLogin();
|
||||
}
|
||||
|
||||
void RemoteClient::doRequestPasswordSalt()
|
||||
{
|
||||
setStatus(StatusGettingPasswordSalt);
|
||||
Command_RequestPasswordSalt cmdRqSalt;
|
||||
cmdRqSalt.set_user_name(userName.toStdString());
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdRqSalt);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::passwordSaltResponse);
|
||||
sendCommand(pend);
|
||||
}
|
||||
|
||||
Command_Login RemoteClient::generateCommandLogin()
|
||||
{
|
||||
Command_Login cmdLogin;
|
||||
cmdLogin.set_user_name(userName.toStdString());
|
||||
cmdLogin.set_clientid(getSrvClientID(lastHostname).toStdString());
|
||||
cmdLogin.set_clientver(VERSION_STRING);
|
||||
|
||||
if (!clientFeatures.isEmpty()) {
|
||||
QMap<QString, bool>::iterator i;
|
||||
for (i = clientFeatures.begin(); i != clientFeatures.end(); ++i)
|
||||
cmdLogin.add_clientfeatures(i.key().toStdString().c_str());
|
||||
}
|
||||
|
||||
return cmdLogin;
|
||||
}
|
||||
|
||||
void RemoteClient::doLogin()
|
||||
{
|
||||
if (!password.isEmpty() && serverSupportsPasswordHash) {
|
||||
// TODO store and log in using stored hashed password
|
||||
if (hashedPassword.isEmpty()) {
|
||||
doRequestPasswordSalt(); // ask salt to create hashedPassword, then log in
|
||||
} else {
|
||||
doHashedLogin(); // log in using hashed password instead
|
||||
}
|
||||
} else {
|
||||
// TODO add setting for client to reject unhashed logins
|
||||
setStatus(StatusLoggingIn);
|
||||
Command_Login cmdLogin = generateCommandLogin();
|
||||
if (!password.isEmpty()) {
|
||||
qCWarning(RemoteClientLog) << "using plain text password to log in";
|
||||
cmdLogin.set_password(password.toStdString());
|
||||
}
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdLogin);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::loginResponse);
|
||||
sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::doHashedLogin()
|
||||
{
|
||||
setStatus(StatusLoggingIn);
|
||||
Command_Login cmdLogin = generateCommandLogin();
|
||||
|
||||
cmdLogin.set_hashed_password(hashedPassword.toStdString());
|
||||
|
||||
PendingCommand *pend = prepareSessionCommand(cmdLogin);
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteClient::loginResponse);
|
||||
sendCommand(pend);
|
||||
}
|
||||
|
||||
void RemoteClient::processConnectionClosedEvent(const Event_ConnectionClosed & /*event*/)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
}
|
||||
|
||||
void RemoteClient::passwordSaltResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
const Response_PasswordSalt &resp = response.GetExtension(Response_PasswordSalt::ext);
|
||||
auto passwordSalt = QString::fromStdString(resp.password_salt());
|
||||
if (passwordSalt.isEmpty()) { // the server does not recognize the user but allows them to enter unregistered
|
||||
password.clear(); // the password will not be used
|
||||
doLogin();
|
||||
} else {
|
||||
hashedPassword = PasswordHasher::computeHash(password, passwordSalt);
|
||||
doHashedLogin();
|
||||
}
|
||||
} else if (response.response_code() != Response::RespNotConnected) {
|
||||
emit loginError(response.response_code(), {}, 0, {});
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::loginResponse(const Response &response)
|
||||
{
|
||||
const Response_Login &resp = response.GetExtension(Response_Login::ext);
|
||||
|
||||
QString possibleMissingFeatures;
|
||||
if (resp.missing_features_size() > 0) {
|
||||
for (int i = 0; i < resp.missing_features_size(); ++i)
|
||||
possibleMissingFeatures.append("," + QString::fromStdString(resp.missing_features(i)));
|
||||
}
|
||||
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
setStatus(StatusLoggedIn);
|
||||
emit userInfoChanged(resp.user_info());
|
||||
|
||||
QList<ServerInfo_User> buddyList;
|
||||
for (int i = resp.buddy_list_size() - 1; i >= 0; --i)
|
||||
buddyList.append(resp.buddy_list(i));
|
||||
emit buddyListReceived(buddyList);
|
||||
|
||||
QList<ServerInfo_User> ignoreList;
|
||||
for (int i = resp.ignore_list_size() - 1; i >= 0; --i)
|
||||
ignoreList.append(resp.ignore_list(i));
|
||||
emit ignoreListReceived(ignoreList);
|
||||
|
||||
if (newMissingFeatureFound(possibleMissingFeatures) && resp.missing_features_size() > 0 &&
|
||||
SettingsCache::instance().getNotifyAboutUpdates()) {
|
||||
SettingsCache::instance().setKnownMissingFeatures(possibleMissingFeatures);
|
||||
emit notifyUserAboutUpdate();
|
||||
}
|
||||
|
||||
} else if (response.response_code() != Response::RespNotConnected) {
|
||||
QList<QString> missingFeatures;
|
||||
if (resp.missing_features_size() > 0) {
|
||||
for (int i = 0; i < resp.missing_features_size(); ++i)
|
||||
missingFeatures << QString::fromStdString(resp.missing_features(i));
|
||||
}
|
||||
emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
|
||||
static_cast<quint32>(resp.denied_end_time()), missingFeatures);
|
||||
setStatus(StatusDisconnecting);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::registerResponse(const Response &response)
|
||||
{
|
||||
const Response_Register &resp = response.GetExtension(Response_Register::ext);
|
||||
switch (response.response_code()) {
|
||||
case Response::RespRegistrationAccepted:
|
||||
emit registerAccepted();
|
||||
doLogin();
|
||||
break;
|
||||
case Response::RespRegistrationAcceptedNeedsActivation:
|
||||
emit registerAcceptedNeedsActivate();
|
||||
doLogin();
|
||||
break;
|
||||
case Response::RespNotConnected:
|
||||
// this response is created by the client from doDisconnectFromServer, do not call it again!
|
||||
emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
|
||||
static_cast<quint32>(resp.denied_end_time()));
|
||||
break;
|
||||
default:
|
||||
emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
|
||||
static_cast<quint32>(resp.denied_end_time()));
|
||||
setStatus(StatusDisconnecting);
|
||||
doDisconnectFromServer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::activateResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespActivationAccepted) {
|
||||
emit activateAccepted();
|
||||
|
||||
doLogin();
|
||||
} else {
|
||||
emit activateError();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::readData()
|
||||
{
|
||||
lastDataReceived = timeRunning;
|
||||
QByteArray data = socket->readAll();
|
||||
|
||||
inputBuffer.append(data);
|
||||
|
||||
do {
|
||||
if (!messageInProgress) {
|
||||
if (inputBuffer.size() >= 4) {
|
||||
// dirty hack to be compatible with v14 server that sends 60 bytes of garbage at the beginning
|
||||
if (!handshakeStarted) {
|
||||
handshakeStarted = true;
|
||||
if (inputBuffer.startsWith("<?xm")) {
|
||||
messageInProgress = true;
|
||||
messageLength = 60;
|
||||
}
|
||||
} else {
|
||||
// end of hack
|
||||
messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) +
|
||||
(((quint32)(unsigned char)inputBuffer[1]) << 16) +
|
||||
(((quint32)(unsigned char)inputBuffer[2]) << 8) +
|
||||
((quint32)(unsigned char)inputBuffer[3]);
|
||||
inputBuffer.remove(0, 4);
|
||||
messageInProgress = true;
|
||||
}
|
||||
} else
|
||||
return;
|
||||
}
|
||||
if (inputBuffer.size() < messageLength)
|
||||
return;
|
||||
|
||||
ServerMessage newServerMessage;
|
||||
newServerMessage.ParseFromArray(inputBuffer.data(), messageLength);
|
||||
|
||||
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
|
||||
|
||||
inputBuffer.remove(0, messageLength);
|
||||
messageInProgress = false;
|
||||
|
||||
processProtocolItem(newServerMessage);
|
||||
|
||||
if (getStatus() == StatusDisconnecting) // use thread-safe getter
|
||||
doDisconnectFromServer();
|
||||
} while (!inputBuffer.isEmpty());
|
||||
}
|
||||
|
||||
void RemoteClient::websocketMessageReceived(const QByteArray &message)
|
||||
{
|
||||
lastDataReceived = timeRunning;
|
||||
ServerMessage newServerMessage;
|
||||
newServerMessage.ParseFromArray(message.data(), message.length());
|
||||
|
||||
qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage);
|
||||
|
||||
processProtocolItem(newServerMessage);
|
||||
}
|
||||
|
||||
void RemoteClient::sendCommandContainer(const CommandContainer &cont)
|
||||
{
|
||||
#if GOOGLE_PROTOBUF_VERSION > 3001000
|
||||
auto size = static_cast<unsigned int>(cont.ByteSizeLong());
|
||||
#else
|
||||
auto size = static_cast<unsigned int>(cont.ByteSize());
|
||||
#endif
|
||||
|
||||
qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont);
|
||||
|
||||
QByteArray buf;
|
||||
if (usingWebSocket) {
|
||||
buf.resize(size);
|
||||
cont.SerializeToArray(buf.data(), size);
|
||||
websocket->sendBinaryMessage(buf);
|
||||
} else {
|
||||
buf.resize(size + 4);
|
||||
cont.SerializeToArray(buf.data() + 4, size);
|
||||
buf.data()[3] = (unsigned char)size;
|
||||
buf.data()[2] = (unsigned char)(size >> 8);
|
||||
buf.data()[1] = (unsigned char)(size >> 16);
|
||||
buf.data()[0] = (unsigned char)(size >> 24);
|
||||
|
||||
socket->write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::connectToHost(const QString &hostname, unsigned int port)
|
||||
{
|
||||
usingWebSocket = port == 443 || port == 80 || port == 4748 || port == 8080;
|
||||
if (usingWebSocket) {
|
||||
QUrl url(QString("%1://%2:%3/servatrice").arg(port == 443 ? "wss" : "ws").arg(hostname).arg(port));
|
||||
websocket->open(url);
|
||||
} else {
|
||||
socket->connectToHost(hostname, static_cast<quint16>(port));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::doConnectToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
userName = _userName;
|
||||
password = _password;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(hostname, port);
|
||||
setStatus(StatusConnecting);
|
||||
}
|
||||
|
||||
void RemoteClient::doRegisterToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password,
|
||||
const QString &_email,
|
||||
const QString &_country,
|
||||
const QString &_realname)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
userName = _userName;
|
||||
password = _password;
|
||||
email = _email;
|
||||
country = _country;
|
||||
realName = _realname;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(hostname, port);
|
||||
setStatus(StatusRegistering);
|
||||
}
|
||||
|
||||
void RemoteClient::doActivateToServer(const QString &_token)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
token = _token.trimmed();
|
||||
|
||||
connectToHost(lastHostname, lastPort);
|
||||
setStatus(StatusActivating);
|
||||
}
|
||||
|
||||
void RemoteClient::doDisconnectFromServer()
|
||||
{
|
||||
timer->stop();
|
||||
|
||||
messageInProgress = false;
|
||||
handshakeStarted = false;
|
||||
messageLength = 0;
|
||||
|
||||
QList<PendingCommand *> pc = pendingCommands.values();
|
||||
for (const auto &i : pc) {
|
||||
Response response;
|
||||
response.set_response_code(Response::RespNotConnected);
|
||||
response.set_cmd_id(i->getCommandContainer().cmd_id());
|
||||
i->processResponse(response);
|
||||
|
||||
delete i;
|
||||
}
|
||||
pendingCommands.clear();
|
||||
|
||||
setStatus(StatusDisconnected);
|
||||
if (websocket->isValid())
|
||||
websocket->close();
|
||||
socket->close();
|
||||
}
|
||||
|
||||
void RemoteClient::ping()
|
||||
{
|
||||
QMutableMapIterator<int, PendingCommand *> i(pendingCommands);
|
||||
while (i.hasNext()) {
|
||||
PendingCommand *pend = i.next().value();
|
||||
if (pend->tick() > maxTimeout) {
|
||||
i.remove();
|
||||
pend->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
int maxTime = timeRunning - lastDataReceived;
|
||||
emit maxPingTime(maxTime, maxTimeout);
|
||||
if (maxTime >= maxTimeout) {
|
||||
disconnectFromServer();
|
||||
emit serverTimeout();
|
||||
} else {
|
||||
sendCommand(prepareSessionCommand(Command_Ping()));
|
||||
++timeRunning;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::connectToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password)
|
||||
{
|
||||
emit sigConnectToServer(hostname, port, _userName, _password);
|
||||
}
|
||||
|
||||
void RemoteClient::registerToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password,
|
||||
const QString &_email,
|
||||
const QString &_country,
|
||||
const QString &_realname)
|
||||
{
|
||||
emit sigRegisterToServer(hostname, port, _userName, _password, _email, _country, _realname);
|
||||
}
|
||||
|
||||
void RemoteClient::activateToServer(const QString &_token)
|
||||
{
|
||||
emit sigActivateToServer(_token.trimmed());
|
||||
}
|
||||
|
||||
void RemoteClient::disconnectFromServer()
|
||||
{
|
||||
emit sigDisconnectFromServer();
|
||||
}
|
||||
|
||||
QString RemoteClient::getSrvClientID(const QString &_hostname)
|
||||
{
|
||||
QString srvClientID = SettingsCache::instance().getClientID();
|
||||
QHostInfo hostInfo = QHostInfo::fromName(_hostname);
|
||||
if (!hostInfo.error()) {
|
||||
QHostAddress hostAddress = hostInfo.addresses().first();
|
||||
srvClientID += hostAddress.toString();
|
||||
} else {
|
||||
qCWarning(RemoteClientLog) << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]";
|
||||
srvClientID += _hostname;
|
||||
}
|
||||
QString uniqueServerClientID =
|
||||
QCryptographicHash::hash(srvClientID.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
|
||||
return uniqueServerClientID;
|
||||
}
|
||||
|
||||
bool RemoteClient::newMissingFeatureFound(const QString &_serversMissingFeatures)
|
||||
{
|
||||
bool newMissingFeature = false;
|
||||
QStringList serversMissingFeaturesList = _serversMissingFeatures.split(",");
|
||||
for (const QString &feature : serversMissingFeaturesList) {
|
||||
if (!feature.isEmpty()) {
|
||||
if (!SettingsCache::instance().getKnownMissingFeatures().contains(feature))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return newMissingFeature;
|
||||
}
|
||||
|
||||
void RemoteClient::clearNewClientFeatures()
|
||||
{
|
||||
QString newKnownMissingFeatures;
|
||||
QStringList existingKnownMissingFeatures = SettingsCache::instance().getKnownMissingFeatures().split(",");
|
||||
for (const QString &existingKnownFeature : existingKnownMissingFeatures) {
|
||||
if (!existingKnownFeature.isEmpty()) {
|
||||
if (!clientFeatures.contains(existingKnownFeature))
|
||||
newKnownMissingFeatures.append("," + existingKnownFeature);
|
||||
}
|
||||
}
|
||||
SettingsCache::instance().setKnownMissingFeatures(newKnownMissingFeatures);
|
||||
}
|
||||
|
||||
void RemoteClient::requestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName)
|
||||
{
|
||||
emit sigRequestForgotPasswordToServer(hostname, port, _userName);
|
||||
}
|
||||
|
||||
void RemoteClient::submitForgotPasswordResetToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_token,
|
||||
const QString &_newpassword)
|
||||
{
|
||||
emit sigSubmitForgotPasswordResetToServer(hostname, port, _userName, _token.trimmed(), _newpassword);
|
||||
}
|
||||
|
||||
void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
userName = _userName;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
|
||||
connectToHost(lastHostname, lastPort);
|
||||
setStatus(StatusRequestingForgotPassword);
|
||||
}
|
||||
|
||||
void RemoteClient::requestForgotPasswordResponse(const Response &response)
|
||||
{
|
||||
const Response_ForgotPasswordRequest &resp = response.GetExtension(Response_ForgotPasswordRequest::ext);
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
if (resp.challenge_email()) {
|
||||
emit sigPromptForForgotPasswordChallenge();
|
||||
} else
|
||||
emit sigPromptForForgotPasswordReset();
|
||||
} else
|
||||
emit sigForgotPasswordError();
|
||||
|
||||
doDisconnectFromServer();
|
||||
}
|
||||
|
||||
void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_token,
|
||||
const QString &_newpassword)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
userName = _userName;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
token = _token.trimmed();
|
||||
password = _newpassword;
|
||||
hashedPassword.clear();
|
||||
|
||||
connectToHost(lastHostname, lastPort);
|
||||
setStatus(StatusSubmitForgotPasswordReset);
|
||||
}
|
||||
|
||||
void RemoteClient::submitForgotPasswordResetResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
emit sigForgotPasswordSuccess();
|
||||
} else
|
||||
emit sigForgotPasswordError();
|
||||
|
||||
doDisconnectFromServer();
|
||||
}
|
||||
|
||||
void RemoteClient::submitForgotPasswordChallengeToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_email)
|
||||
{
|
||||
emit sigSubmitForgotPasswordChallengeToServer(hostname, port, _userName, _email);
|
||||
}
|
||||
|
||||
void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_email)
|
||||
{
|
||||
doDisconnectFromServer();
|
||||
|
||||
userName = _userName;
|
||||
lastHostname = hostname;
|
||||
lastPort = port;
|
||||
email = _email;
|
||||
|
||||
connectToHost(lastHostname, lastPort);
|
||||
setStatus(StatusSubmitForgotPasswordChallenge);
|
||||
}
|
||||
|
||||
void RemoteClient::submitForgotPasswordChallengeResponse(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
emit sigPromptForForgotPasswordReset();
|
||||
} else
|
||||
emit sigForgotPasswordError();
|
||||
|
||||
doDisconnectFromServer();
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* @file remote_client.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef REMOTECLIENT_H
|
||||
#define REMOTECLIENT_H
|
||||
|
||||
#include "../abstract/abstract_client.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocket>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(RemoteClientLog, "remote_client");
|
||||
|
||||
class QTimer;
|
||||
|
||||
class RemoteClient : public AbstractClient
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void serverTimeout();
|
||||
void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
|
||||
void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime);
|
||||
void activateError();
|
||||
void socketError(const QString &errorString);
|
||||
void protocolVersionMismatch(int clientVersion, int serverVersion);
|
||||
void
|
||||
sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
|
||||
void sigRegisterToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password,
|
||||
const QString &_email,
|
||||
const QString &_country,
|
||||
const QString &_realname);
|
||||
void sigActivateToServer(const QString &_token);
|
||||
void sigDisconnectFromServer();
|
||||
void notifyUserAboutUpdate();
|
||||
void sigRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
|
||||
void sigForgotPasswordSuccess();
|
||||
void sigForgotPasswordError();
|
||||
void sigPromptForForgotPasswordReset();
|
||||
void sigSubmitForgotPasswordResetToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_token,
|
||||
const QString &_newpassword);
|
||||
void sigPromptForForgotPasswordChallenge();
|
||||
void sigSubmitForgotPasswordChallengeToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_email);
|
||||
private slots:
|
||||
void slotConnected();
|
||||
void readData();
|
||||
void websocketMessageReceived(const QByteArray &message);
|
||||
void slotSocketError(QAbstractSocket::SocketError error);
|
||||
void slotWebSocketError(QAbstractSocket::SocketError error);
|
||||
void ping();
|
||||
void processServerIdentificationEvent(const Event_ServerIdentification &event);
|
||||
void processConnectionClosedEvent(const Event_ConnectionClosed &event);
|
||||
void passwordSaltResponse(const Response &response);
|
||||
void loginResponse(const Response &response);
|
||||
void registerResponse(const Response &response);
|
||||
void activateResponse(const Response &response);
|
||||
void
|
||||
doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
|
||||
void doRegisterToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password,
|
||||
const QString &_email,
|
||||
const QString &_country,
|
||||
const QString &_realname);
|
||||
void doRequestPasswordSalt();
|
||||
void doLogin();
|
||||
void doHashedLogin();
|
||||
Command_Login generateCommandLogin();
|
||||
void doDisconnectFromServer();
|
||||
void doActivateToServer(const QString &_token);
|
||||
void doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
|
||||
void requestForgotPasswordResponse(const Response &response);
|
||||
void doSubmitForgotPasswordResetToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_token,
|
||||
const QString &_newpassword);
|
||||
void submitForgotPasswordResetResponse(const Response &response);
|
||||
void doSubmitForgotPasswordChallengeToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_email);
|
||||
void submitForgotPasswordChallengeResponse(const Response &response);
|
||||
|
||||
private:
|
||||
int maxTimeout;
|
||||
int timeRunning, lastDataReceived;
|
||||
QByteArray inputBuffer;
|
||||
bool messageInProgress;
|
||||
bool handshakeStarted;
|
||||
bool usingWebSocket;
|
||||
int messageLength;
|
||||
QTimer *timer;
|
||||
QTcpSocket *socket;
|
||||
QWebSocket *websocket;
|
||||
QString lastHostname;
|
||||
unsigned int lastPort;
|
||||
QString hashedPassword;
|
||||
|
||||
QString getSrvClientID(const QString &_hostname);
|
||||
bool newMissingFeatureFound(const QString &_serversMissingFeatures);
|
||||
void clearNewClientFeatures();
|
||||
void connectToHost(const QString &hostname, unsigned int port);
|
||||
|
||||
protected slots:
|
||||
void sendCommandContainer(const CommandContainer &cont) override;
|
||||
|
||||
public:
|
||||
explicit RemoteClient(QObject *parent = nullptr);
|
||||
~RemoteClient() override;
|
||||
QString peerName() const
|
||||
{
|
||||
if (usingWebSocket) {
|
||||
return websocket->peerName();
|
||||
} else {
|
||||
return socket->peerName();
|
||||
}
|
||||
}
|
||||
void
|
||||
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
|
||||
void registerToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_password,
|
||||
const QString &_email,
|
||||
const QString &_country,
|
||||
const QString &_realname);
|
||||
void activateToServer(const QString &_token);
|
||||
void disconnectFromServer();
|
||||
void requestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName);
|
||||
void submitForgotPasswordResetToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_token,
|
||||
const QString &_newpassword);
|
||||
void submitForgotPasswordChallengeToServer(const QString &hostname,
|
||||
unsigned int port,
|
||||
const QString &_userName,
|
||||
const QString &_email);
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue