Changeset View
Standalone View
source/network/scripting/JSInterface_Network.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | |||||||||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | |||||||||||
* | * | |||||||||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | |||||||||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | |||||||||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | |||||||||||
* (at your option) any later version. | * (at your option) any later version. | |||||||||||
* | * | |||||||||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | |||||||||||
Show All 14 Lines | ||||||||||||
#include "lib/types.h" | #include "lib/types.h" | |||||||||||
#include "lobby/IXmppClient.h" | #include "lobby/IXmppClient.h" | |||||||||||
#include "network/NetClient.h" | #include "network/NetClient.h" | |||||||||||
#include "network/NetMessage.h" | #include "network/NetMessage.h" | |||||||||||
#include "network/NetServer.h" | #include "network/NetServer.h" | |||||||||||
#include "network/StunClient.h" | #include "network/StunClient.h" | |||||||||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | |||||||||||
#include "ps/Game.h" | #include "ps/Game.h" | |||||||||||
#include "ps/Util.h" | ||||||||||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | |||||||||||
#include "third_party/encryption/pkcs5_pbkdf2.h" | ||||||||||||
u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | |||||||||||
{ | { | |||||||||||
return PS_DEFAULT_PORT; | return PS_DEFAULT_PORT; | |||||||||||
} | } | |||||||||||
bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | |||||||||||
{ | { | |||||||||||
return !!g_NetServer; | return !!g_NetServer; | |||||||||||
} | } | |||||||||||
bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | |||||||||||
{ | { | |||||||||||
return !!g_NetClient; | return !!g_NetClient; | |||||||||||
} | } | |||||||||||
JS::Value JSI_Network::FindStunEndpoint(ScriptInterface::CmptPrivate* pCmptPrivate, int port) | CStr JSI_Network::HashPassword(const CStr& password) | |||||||||||
{ | { | |||||||||||
return StunClient::FindStunEndpointHost(*(pCmptPrivate->pScriptInterface), port); | if (password.empty()) | |||||||||||
return password; | ||||||||||||
ENSURE(sodium_init() >= 0); | ||||||||||||
const int DIGESTSIZE = crypto_hash_sha256_BYTES; | ||||||||||||
const int ITERATIONS = 1737; | ||||||||||||
Stan: Could be constexpr? | ||||||||||||
Done Inline Actionswhat is benefit of constexpr on int literal ? Silier: what is benefit of constexpr on int literal ? | ||||||||||||
Not Done Inline ActionsOne is known at compile time the other isn't Stan: One is known at compile time the other isn't
http://www.vishalchovatiya.com/when-to-use-const… | ||||||||||||
cassert(DIGESTSIZE == 32); | ||||||||||||
static const unsigned char salt_base[DIGESTSIZE] = { | ||||||||||||
244, 243, 249, 244, 32, 33, 19, 35, 16, 11, 12, 13, 14, 15, 16, 17, | ||||||||||||
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 88, 123, 234, 123 }; | ||||||||||||
// initialize the salt buffer | ||||||||||||
unsigned char salt_buffer[DIGESTSIZE] = { 0 }; | ||||||||||||
crypto_hash_sha256_state state; | ||||||||||||
crypto_hash_sha256_init(&state); | ||||||||||||
crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base)); | ||||||||||||
crypto_hash_sha256_final(&state, salt_buffer); | ||||||||||||
// PBKDF2 to create the buffer | ||||||||||||
unsigned char encrypted[DIGESTSIZE]; | ||||||||||||
pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS); | ||||||||||||
return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase(); | ||||||||||||
} | } | |||||||||||
void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName) | void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password) | |||||||||||
Not Done Inline ActionsI think you need the UNUSED2 macro on pCmptPrivate to make the logs happy Stan: I think you need the UNUSED2 macro on pCmptPrivate to make the logs happy | ||||||||||||
Not Done Inline ActionsIt's used here, it's below wraitii: It's used here, it's below | ||||||||||||
{ | { | |||||||||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | |||||||||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | |||||||||||
ENSURE(!g_Game); | ENSURE(!g_Game); | |||||||||||
// Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames. | // Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames. | |||||||||||
g_NetServer = new CNetServer(!!g_XmppClient); | bool hasLobby = !!g_XmppClient; | |||||||||||
g_NetServer = new CNetServer(hasLobby); | ||||||||||||
// In lobby, we send our public ip and port on request to the players, who want to connect. | ||||||||||||
// In both cases we need to ping stun server to get our public ip. If we want to host via stun, | ||||||||||||
// we need port as well. | ||||||||||||
if (hasLobby) | ||||||||||||
{ | ||||||||||||
CStr ip; | ||||||||||||
if (!useSTUN) | ||||||||||||
{ | ||||||||||||
Done Inline ActionsSo you print twice an error? Stan: So you print twice an error? | ||||||||||||
if (!StunClient::GetPublicIp(ip, serverPort)) | ||||||||||||
{ | ||||||||||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ||||||||||||
ScriptException::Raise(rq, "Failed to get public ip."); | ||||||||||||
SAFE_DELETE(g_NetServer); | ||||||||||||
return; | ||||||||||||
} | ||||||||||||
g_NetServer->SetConnectionData(ip, serverPort, false); | ||||||||||||
} | ||||||||||||
Done Inline ActionsQ: this seems necessary because the host shares the IP, whereas before the lobby shared the host's IP? I'm not entirely sure why we add this. I think the "We need to get public ip" should be detailed with the "why" wraitii: Q: this seems necessary because the host shares the IP, whereas before the lobby shared the… | ||||||||||||
Done Inline ActionsIf user chooses to use stun in js, it gets stun endpoint and port there and if fails, it even doesnt call this function, if he gets stun endpoint, we do not want to call it here again so we pass it here. Silier: If user chooses to use stun in js, it gets stun endpoint and port there and if fails, it even… | ||||||||||||
Done Inline Actionsand yes, host needs to know his public ip, better to do it once Silier: and yes, host needs to know his public ip, better to do it once | ||||||||||||
Done Inline ActionsI think part of my confusion is that the StunIP is queried in JS, but the non-stun IP is not. I feel like just doing everything in C++ (or everything in JS) would be simpler. wraitii: I think part of my confusion is that the StunIP is queried in JS, but the non-stun IP is not. I… | ||||||||||||
else | ||||||||||||
{ | ||||||||||||
u16 port = serverPort; | ||||||||||||
// This is using port variable to store return value, do not pass serverPort itself. | ||||||||||||
if (!StunClient::FindStunEndpointHost(ip, port)) | ||||||||||||
{ | ||||||||||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ||||||||||||
ScriptException::Raise(rq, "Failed to host via STUN."); | ||||||||||||
SAFE_DELETE(g_NetServer); | ||||||||||||
return; | ||||||||||||
} | ||||||||||||
g_NetServer->SetConnectionData(ip, port, true); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
if (!g_NetServer->SetupConnection(serverPort)) | if (!g_NetServer->SetupConnection(serverPort)) | |||||||||||
{ | { | |||||||||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | |||||||||||
ScriptException::Raise(rq, "Failed to start server"); | ScriptException::Raise(rq, "Failed to start server"); | |||||||||||
SAFE_DELETE(g_NetServer); | SAFE_DELETE(g_NetServer); | |||||||||||
return; | return; | |||||||||||
} | } | |||||||||||
// We will get hashed password from clients, so hash it once for server | ||||||||||||
g_NetServer->SetPassword(HashPassword(password)); | ||||||||||||
g_Game = new CGame(true); | g_Game = new CGame(true); | |||||||||||
g_NetClient = new CNetClient(g_Game, true); | g_NetClient = new CNetClient(g_Game, true); | |||||||||||
g_NetClient->SetUserName(playerName); | g_NetClient->SetUserName(playerName); | |||||||||||
g_NetClient->SetHostingPlayerName(hostLobbyName); | g_NetClient->SetHostingPlayerName(hostLobbyName); | |||||||||||
g_NetClient->SetupServerData("127.0.0.1", serverPort, false); | ||||||||||||
if (!g_NetClient->SetupConnection("127.0.0.1", serverPort, nullptr)) | if (!g_NetClient->SetupConnection(nullptr)) | |||||||||||
{ | { | |||||||||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | |||||||||||
ScriptException::Raise(rq, "Failed to connect to server"); | ScriptException::Raise(rq, "Failed to connect to server"); | |||||||||||
SAFE_DELETE(g_NetClient); | SAFE_DELETE(g_NetClient); | |||||||||||
SAFE_DELETE(g_Game); | SAFE_DELETE(g_Game); | |||||||||||
} | } | |||||||||||
} | } | |||||||||||
void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID) | void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID) | |||||||||||
Not Done Inline Actions
UNUSED wraitii: UNUSED | ||||||||||||
Done Inline ActionsL162 :) Silier: L162 :) | ||||||||||||
{ | { | |||||||||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | |||||||||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | |||||||||||
ENSURE(!g_Game); | ENSURE(!g_Game); | |||||||||||
ENetHost* enetClient = nullptr; | ENetHost* enetClient = nullptr; | |||||||||||
if (g_XmppClient && useSTUN) | if (g_XmppClient && useSTUN) | |||||||||||
{ | { | |||||||||||
Show All 26 Lines | if (g_XmppClient && useSTUN) | |||||||||||
SDL_Delay(1000); | SDL_Delay(1000); | |||||||||||
} | } | |||||||||||
g_Game = new CGame(true); | g_Game = new CGame(true); | |||||||||||
g_NetClient = new CNetClient(g_Game, false); | g_NetClient = new CNetClient(g_Game, false); | |||||||||||
g_NetClient->SetUserName(playerName); | g_NetClient->SetUserName(playerName); | |||||||||||
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | |||||||||||
g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); | ||||||||||||
if (g_XmppClient && useSTUN) | if (g_XmppClient && useSTUN) | |||||||||||
StunClient::SendHolePunchingMessages(*enetClient, serverAddress, serverPort); | StunClient::SendHolePunchingMessages(*enetClient, serverAddress, serverPort); | |||||||||||
if (!g_NetClient->SetupConnection(serverAddress, serverPort, enetClient)) | if (!g_NetClient->SetupConnection(enetClient)) | |||||||||||
{ | { | |||||||||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | |||||||||||
ScriptException::Raise(rq, "Failed to connect to server"); | ScriptException::Raise(rq, "Failed to connect to server"); | |||||||||||
SAFE_DELETE(g_NetClient); | SAFE_DELETE(g_NetClient); | |||||||||||
SAFE_DELETE(g_Game); | SAFE_DELETE(g_Game); | |||||||||||
} | } | |||||||||||
} | } | |||||||||||
void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID, const CStr& password) | ||||||||||||
Done Inline Actions
bb: | ||||||||||||
{ | ||||||||||||
Done Inline ActionsLikewise, I think this won't be obvious in the future. Should specify that this is because sharing IPs is done by proxy, and why this is needed. wraitii: Likewise, I think this won't be obvious in the future. Should specify that this is because… | ||||||||||||
ENSURE(!!g_XmppClient); | ||||||||||||
g_Game = new CGame(true); | ||||||||||||
g_NetClient = new CNetClient(g_Game, false); | ||||||||||||
g_NetClient->SetUserName(playerName); | ||||||||||||
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | ||||||||||||
g_XmppClient->SendIqGetConnectionData(hostJID, HashPassword(password).c_str()); | ||||||||||||
} | ||||||||||||
void JSI_Network::DisconnectNetworkGame(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | void JSI_Network::DisconnectNetworkGame(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | |||||||||||
{ | { | |||||||||||
// TODO: we ought to do async reliable disconnections | // TODO: we ought to do async reliable disconnections | |||||||||||
SAFE_DELETE(g_NetServer); | SAFE_DELETE(g_NetServer); | |||||||||||
SAFE_DELETE(g_NetClient); | SAFE_DELETE(g_NetClient); | |||||||||||
SAFE_DELETE(g_Game); | SAFE_DELETE(g_Game); | |||||||||||
} | } | |||||||||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | else | |||||||||||
LOGERROR("Only network host can change turn length"); | LOGERROR("Only network host can change turn length"); | |||||||||||
} | } | |||||||||||
void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface) | void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface) | |||||||||||
{ | { | |||||||||||
scriptInterface.RegisterFunction<u16, &GetDefaultPort>("GetDefaultPort"); | scriptInterface.RegisterFunction<u16, &GetDefaultPort>("GetDefaultPort"); | |||||||||||
scriptInterface.RegisterFunction<bool, &HasNetServer>("HasNetServer"); | scriptInterface.RegisterFunction<bool, &HasNetServer>("HasNetServer"); | |||||||||||
scriptInterface.RegisterFunction<bool, &HasNetClient>("HasNetClient"); | scriptInterface.RegisterFunction<bool, &HasNetClient>("HasNetClient"); | |||||||||||
scriptInterface.RegisterFunction<JS::Value, int, &FindStunEndpoint>("FindStunEndpoint"); | scriptInterface.RegisterFunction<void, CStrW, u16, CStr, bool, CStr, &StartNetworkHost>("StartNetworkHost"); | |||||||||||
scriptInterface.RegisterFunction<void, CStrW, u16, CStr, &StartNetworkHost>("StartNetworkHost"); | ||||||||||||
scriptInterface.RegisterFunction<void, CStrW, CStr, u16, bool, CStr, &StartNetworkJoin>("StartNetworkJoin"); | scriptInterface.RegisterFunction<void, CStrW, CStr, u16, bool, CStr, &StartNetworkJoin>("StartNetworkJoin"); | |||||||||||
scriptInterface.RegisterFunction<void, CStrW, CStr, CStr, &StartNetworkJoinLobby>("StartNetworkJoinLobby"); | ||||||||||||
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame"); | scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame"); | |||||||||||
scriptInterface.RegisterFunction<CStr, &GetPlayerGUID>("GetPlayerGUID"); | scriptInterface.RegisterFunction<CStr, &GetPlayerGUID>("GetPlayerGUID"); | |||||||||||
scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient"); | scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient"); | |||||||||||
scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes"); | scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes"); | |||||||||||
scriptInterface.RegisterFunction<void, int, CStr, &AssignNetworkPlayer>("AssignNetworkPlayer"); | scriptInterface.RegisterFunction<void, int, CStr, &AssignNetworkPlayer>("AssignNetworkPlayer"); | |||||||||||
scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer"); | scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer"); | |||||||||||
scriptInterface.RegisterFunction<void, CStrW, &SendNetworkChat>("SendNetworkChat"); | scriptInterface.RegisterFunction<void, CStrW, &SendNetworkChat>("SendNetworkChat"); | |||||||||||
scriptInterface.RegisterFunction<void, int, &SendNetworkReady>("SendNetworkReady"); | scriptInterface.RegisterFunction<void, int, &SendNetworkReady>("SendNetworkReady"); | |||||||||||
scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady"); | scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady"); | |||||||||||
scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame"); | scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame"); | |||||||||||
scriptInterface.RegisterFunction<void, int, &SetTurnLength>("SetTurnLength"); | scriptInterface.RegisterFunction<void, int, &SetTurnLength>("SetTurnLength"); | |||||||||||
} | } |
Could be constexpr?