Files
OpenSpace/src/network/parallelserver.cpp
Alexander Bock bb3db7ada7 Feature/jenkins fix (#816)
* Cleanup
* CMake cleanups
* Update current year
* Update copyright header
* Use script to return list of all modules
* Update credits, license and ghoul
2019-03-24 11:19:39 +01:00

435 lines
14 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2019 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/network/parallelserver.h>
#include <ghoul/fmt.h>
#include <ghoul/io/socket/tcpsocket.h>
#include <ghoul/logging/logmanager.h>
#include <functional>
// @TODO(abock): In the entire class remove std::shared_ptr<Peer> by const Peer& where
// possible to simplify the interface
namespace {
constexpr const char* _loggerCat = "ParallelServer";
} // namespace
namespace openspace {
void ParallelServer::start(int port, const std::string& password,
const std::string& changeHostPassword)
{
_socketServer.listen(port);
_passwordHash = std::hash<std::string>{}(password);
_changeHostPasswordHash = std::hash<std::string>{}(changeHostPassword);
_serverThread = std::thread([this](){ handleNewPeers(); });
_eventLoopThread = std::thread([this]() { eventLoop(); });
}
void ParallelServer::setDefaultHostAddress(std::string defaultHostAddress) {
std::lock_guard<std::mutex> lock(_hostInfoMutex);
_defaultHostAddress = std::move(defaultHostAddress);
}
std::string ParallelServer::defaultHostAddress() const {
std::lock_guard<std::mutex> lock(_hostInfoMutex);
return _defaultHostAddress;
}
void ParallelServer::stop() {
_shouldStop = true;
_socketServer.close();
}
void ParallelServer::handleNewPeers() {
while (!_shouldStop) {
std::unique_ptr<ghoul::io::TcpSocket> socket =
_socketServer.awaitPendingTcpSocket();
socket->startStreams();
const size_t id = _nextConnectionId++;
std::shared_ptr<Peer> p = std::make_shared<Peer>(Peer{
id,
"",
ParallelConnection(std::move(socket)),
ParallelConnection::Status::Connecting,
std::thread()
});
auto it = _peers.emplace(p->id, p);
it.first->second->thread = std::thread([this, id]() {
handlePeer(id);
});
}
}
std::shared_ptr<ParallelServer::Peer> ParallelServer::peer(size_t id) {
std::lock_guard<std::mutex> lock(_peerListMutex);
auto it = _peers.find(id);
if (it == _peers.end()) {
return nullptr;
}
return it->second;
}
void ParallelServer::handlePeer(size_t id) {
while (!_shouldStop) {
std::shared_ptr<Peer> p = peer(id);
if (!p) {
return;
}
if (!p->parallelConnection.isConnectedOrConnecting()) {
return;
}
try {
ParallelConnection::Message m = p->parallelConnection.receiveMessage();
_incomingMessages.push({id, m});
} catch (const ParallelConnection::ConnectionLostError&) {
LERROR(fmt::format("Connection lost to {}", p->id));
_incomingMessages.push({
id,
ParallelConnection::Message(
ParallelConnection::MessageType::Disconnection, std::vector<char>()
)
});
return;
}
}
}
void ParallelServer::eventLoop() {
while (!_shouldStop) {
PeerMessage pm = _incomingMessages.pop();
handlePeerMessage(std::move(pm));
}
}
void ParallelServer::handlePeerMessage(PeerMessage peerMessage) {
const size_t peerId = peerMessage.peerId;
auto it = _peers.find(peerId);
if (it == _peers.end()) {
return;
}
std::shared_ptr<Peer>& peer = it->second;
const ParallelConnection::MessageType messageType = peerMessage.message.type;
std::vector<char>& data = peerMessage.message.content;
switch (messageType) {
case ParallelConnection::MessageType::Authentication:
handleAuthentication(peer, std::move(data));
break;
case ParallelConnection::MessageType::Data:
handleData(*peer, std::move(data));
break;
case ParallelConnection::MessageType::HostshipRequest:
handleHostshipRequest(peer, std::move(data));
break;
case ParallelConnection::MessageType::HostshipResignation:
handleHostshipResignation(*peer);
break;
case ParallelConnection::MessageType::Disconnection:
disconnect(*peer);
break;
default:
LERROR(fmt::format(
"Unsupported message type: {}", static_cast<int>(messageType)
));
break;
}
}
void ParallelServer::handleAuthentication(std::shared_ptr<Peer> peer,
std::vector<char> message)
{
std::stringstream input(std::string(message.begin(), message.end()));
// 8 bytes passcode
uint64_t passwordHash = 0;
input.read(reinterpret_cast<char*>(&passwordHash), sizeof(uint64_t));
if (passwordHash != _passwordHash) {
LERROR(fmt::format("Connection {} provided incorrect passcode.", peer->id));
disconnect(*peer);
return;
}
// 4 bytes name size
uint32_t nameSize = 0;
input.read(reinterpret_cast<char*>(&nameSize), sizeof(uint32_t));
// <nameSize> bytes name
std::string name(nameSize, static_cast<char>(0));
input.read(&name[0], nameSize);
if (nameSize == 0) {
name = "Anonymous";
}
setName(*peer, name);
LINFO(fmt::format("Connection established with {} \"{}\"", peer->id, name));
std::string defaultHostAddress;
{
std::lock_guard<std::mutex> _hostMutex(_hostInfoMutex);
defaultHostAddress = _defaultHostAddress;
}
if (_hostPeerId == 0 &&
peer->parallelConnection.socket()->address() == defaultHostAddress)
{
// Directly promote the conenction to host (initialize)
// if there is no host, and ip matches default host ip.
LINFO(fmt::format("Connection {} directly promoted to host.", peer->id));
assignHost(peer);
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
// sendConnectionStatus(it->second) ?
sendConnectionStatus(*peer);
}
}
else {
setToClient(*peer);
}
setNConnections(nConnections() + 1);
}
void ParallelServer::handleData(const Peer& peer, std::vector<char> data) {
if (peer.id != _hostPeerId) {
LINFO(fmt::format(
"Connection {} tried to send data without being the host. Ignoring", peer.id
));
}
sendMessageToClients(ParallelConnection::MessageType::Data, data);
}
void ParallelServer::handleHostshipRequest(std::shared_ptr<Peer> peer,
std::vector<char> message)
{
std::stringstream input(std::string(message.begin(), message.end()));
LINFO(fmt::format("Connection {} requested hostship.", peer->id));
uint64_t passwordHash = 0;
input.read(reinterpret_cast<char*>(&passwordHash), sizeof(uint64_t));
if (passwordHash != _changeHostPasswordHash) {
LERROR(fmt::format("Connection {} provided incorrect host password.", peer->id));
return;
}
size_t oldHostPeerId = 0;
{
std::lock_guard<std::mutex> lock(_hostInfoMutex);
oldHostPeerId = _hostPeerId;
}
if (oldHostPeerId == peer->id) {
LINFO(fmt::format("Connection {} is already the host.", peer->id));
return;
}
assignHost(peer);
LINFO(fmt::format("Switched host from {} to {}.", oldHostPeerId, peer->id));
}
void ParallelServer::handleHostshipResignation(Peer& peer) {
LINFO(fmt::format("Connection {} wants to resign its hostship.", peer.id));
size_t oldHostPeerId = 0;
{
std::lock_guard<std::mutex> lock(_hostInfoMutex);
oldHostPeerId = _hostPeerId;
}
setToClient(peer);
LINFO(fmt::format("Connection {} resigned as host.", peer.id));
}
bool ParallelServer::isConnected(const Peer& peer) const {
return peer.status != ParallelConnection::Status::Connecting &&
peer.status != ParallelConnection::Status::Disconnected;
}
void ParallelServer::sendMessage(Peer& peer,
ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
peer.parallelConnection.sendMessage({ messageType, message });
}
void ParallelServer::sendMessageToAll(ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (isConnected(*it.second)) {
it.second->parallelConnection.sendMessage({ messageType, message });
}
}
}
void ParallelServer::sendMessageToClients(ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (it.second->status == ParallelConnection::Status::ClientWithHost) {
it.second->parallelConnection.sendMessage({ messageType, message });
}
}
}
void ParallelServer::disconnect(Peer& peer) {
if (isConnected(peer)) {
setNConnections(nConnections() - 1);
}
size_t hostPeerId = 0;
{
std::lock_guard<std::mutex> lock(_hostInfoMutex);
hostPeerId = _hostPeerId;
}
// Make sure any disconnecting host is first degraded to client,
// in order to notify other clients about host disconnection.
if (peer.id == hostPeerId) {
setToClient(peer);
}
peer.parallelConnection.disconnect();
peer.thread.join();
_peers.erase(peer.id);
}
void ParallelServer::setName(Peer& peer, std::string name) {
peer.name = name;
size_t hostPeerId = 0;
{
std::lock_guard lock(_hostInfoMutex);
hostPeerId = _hostPeerId;
}
// Make sure everyone gets the new host name.
if (peer.id == hostPeerId) {
{
std::lock_guard lock(_hostInfoMutex);
_hostName = name;
}
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
// sendConnectionStatus(it->second) ?
sendConnectionStatus(peer);
}
}
}
void ParallelServer::assignHost(std::shared_ptr<Peer> newHost) {
{
std::lock_guard lock(_hostInfoMutex);
std::shared_ptr<ParallelServer::Peer> oldHost = peer(_hostPeerId);
if (oldHost) {
oldHost->status = ParallelConnection::Status::ClientWithHost;
}
_hostPeerId = newHost->id;
_hostName = newHost->name;
}
newHost->status = ParallelConnection::Status::Host;
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (it.second != newHost) {
it.second->status = ParallelConnection::Status::ClientWithHost;
}
sendConnectionStatus(*it.second);
}
}
void ParallelServer::setToClient(Peer& peer) {
if (peer.status == ParallelConnection::Status::Host) {
{
std::lock_guard lock(_hostInfoMutex);
_hostPeerId = 0;
_hostName = "";
}
// If host becomes client, make all clients hostless.
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
it.second->status = ParallelConnection::Status::ClientWithoutHost;
sendConnectionStatus(*it.second);
}
} else {
peer.status = (_hostPeerId > 0) ?
ParallelConnection::Status::ClientWithHost :
ParallelConnection::Status::ClientWithoutHost;
sendConnectionStatus(peer);
}
}
void ParallelServer::setNConnections(size_t nConnections) {
_nConnections = nConnections;
std::vector<char> data;
const uint32_t n = static_cast<uint32_t>(_nConnections);
data.insert(
data.end(),
reinterpret_cast<const char*>(&n),
reinterpret_cast<const char*>(&n) + sizeof(uint32_t)
);
sendMessageToAll(ParallelConnection::MessageType::NConnections, data);
}
void ParallelServer::sendConnectionStatus(Peer& peer) {
std::vector<char> data;
const uint32_t outStatus = static_cast<uint32_t>(peer.status);
data.insert(
data.end(),
reinterpret_cast<const char*>(&outStatus),
reinterpret_cast<const char*>(&outStatus) + sizeof(uint32_t)
);
const uint32_t outHostNameSize = static_cast<uint32_t>(_hostName.size());
data.insert(
data.end(),
reinterpret_cast<const char*>(&outHostNameSize),
reinterpret_cast<const char*>(&outHostNameSize) + sizeof(uint32_t)
);
data.insert(
data.end(),
reinterpret_cast<const char*>(_hostName.data()),
reinterpret_cast<const char*>(_hostName.data() + outHostNameSize)
);
sendMessage(peer, ParallelConnection::MessageType::ConnectionStatus, data);
}
size_t ParallelServer::nConnections() const {
return _nConnections;
}
} // namespace openspace