This commit is contained in:
Stephen Birarda 2014-10-03 15:35:47 -07:00
commit cbc505e71a
37 changed files with 1225 additions and 543 deletions

View file

@ -58,6 +58,7 @@ endforeach()
# targets on all platforms
add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(ice-server)
add_subdirectory(interface)
add_subdirectory(tests)
add_subdirectory(tools)

View file

@ -12,7 +12,28 @@
"name": "id",
"label": "Domain ID",
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank."
}
},
{
"name": "automatic_networking",
"label": "Automatic Networking",
"help": "This defines how other nodes in the High Fidelity metaverse will be able to reach your domain-server.<br/>If you don't want to deal with any network settings, use full automatic networking.",
"default": "disabled",
"type": "select",
"options": [
{
"value": "full",
"label": "Full: update both the IP address and port to reach my server"
},
{
"value": "ip",
"label": "IP Only: update just my IP address, I will open the port manually"
},
{
"value": "disabled",
"label": "None: use the network information I have entered for this domain at data.highfidelity.io"
}
]
}
]
},
{
@ -53,11 +74,11 @@
},
{
"name": "attenuation_per_doubling_in_distance",
"label": "Attenuattion per doubling in distance",
"label": "Attenuation per doubling in distance",
"help": "Factor between 0.0 and 1.0 (0.0: No attenuation, 1.0: extreme attenuation)",
"placeholder": "0.18",
"default": "0.18",
"advanced": true
"advanced": false
},
{
"name": "dynamic_jitter_buffer",

View file

@ -64,7 +64,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -74,8 +74,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
loadExistingSessionsFromSettings();
// check if we have the flag that enables dynamic IP
setupDynamicIPAddressUpdating();
// setup automatic networking settings with data server
setupAutomaticNetworking();
}
}
@ -156,6 +156,16 @@ bool DomainServer::optionallySetupOAuth() {
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = DEFAULT_NODE_AUTH_URL;
}
AccountManager& accountManager = AccountManager::getInstance();
accountManager.disableSettingsFilePersistence();
accountManager.setAuthURL(_oauthProviderURL);
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
@ -230,41 +240,37 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
addStaticAssignmentsToQueue();
}
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
bool DomainServer::hasOAuthProviderAndAuthInformation() {
bool DomainServer::didSetupAccountManagerWithAccessToken() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken()) {
// we already gave the account manager a valid access token
return true;
}
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN";
static bool hasAttemptedAuthWithOAuthProvider = false;
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
if (!hasAttemptedAuthWithOAuthProvider) {
AccountManager& accountManager = AccountManager::getInstance();
accountManager.setAuthURL(_oauthProviderURL);
if (accessToken.isEmpty()) {
const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accountManager.hasValidAccessToken()) {
// we don't have a valid access token so we need to get one
// check if we have a username and password set via env
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
if (!username.isEmpty() && !password.isEmpty()) {
accountManager.requestAccessToken(username, password);
// connect to loginFailed signal from AccountManager so we can quit if that is the case
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
} else {
qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
return false;
}
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
<< "Set an access token via the web interface, in your user or master config"
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
return false;
}
hasAttemptedAuthWithOAuthProvider = true;
}
// give this access token to the AccountManager
accountManager.setAccessTokenForCurrentAuthURL(accessToken);
return true;
} else {
@ -282,7 +288,7 @@ bool DomainServer::optionallySetupAssignmentPayment() {
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
didSetupAccountManagerWithAccessToken()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
@ -304,49 +310,71 @@ bool DomainServer::optionallySetupAssignmentPayment() {
return true;
}
void DomainServer::setupDynamicIPAddressUpdating() {
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full";
const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip";
const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled";
void DomainServer::setupAutomaticNetworking() {
const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot setup domain-server automatic networking without an access token.";
qDebug() << "Please add an access token to your config file or via the web interface.";
return;
}
if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
QString automaticNetworkValue =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->getSessionUUID();
if (!domainID.isNull()) {
qDebug() << "domain-server IP address will be updated for domain with ID"
qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
// setup our timer to check our IP via stun every 30 seconds
// setup our timer to check our IP via stun every X seconds
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN);
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
// send public socket changes to the data server so nodes can find us at our new IP
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
if (!AccountManager::getInstance().hasValidAccessToken()) {
// we don't have an access token to talk to data-web yet, so
// check our IP address as soon as we get an AccountManager access token
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
this, &DomainServer::requestCurrentIPAddressViaSTUN);
if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
// send public socket changes to the data server so nodes can find us at our new IP
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate);
} else {
// access token good to go, attempt to update our IP now
requestCurrentIPAddressViaSTUN();
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
// setup a timer to heartbeat with the ice-server every so often
QTimer* iceHeartbeatTimer = new QTimer(this);
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
// call our sendHeartbeaToIceServer immediately anytime a public address changes
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer);
// tell the data server which type of automatic networking we are using
updateNetworkingInfoWithDataServer(automaticNetworkValue);
}
} else {
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
<< "Please add an id to your config.json or pass it with the command line argument --id.";
qDebug() << "Failed dynamic IP address update setup. domain-server will now quit.";
// attempt to update our sockets now
requestCurrentPublicSocketViaSTUN();
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
return;
}
} else {
updateNetworkingInfoWithDataServer(automaticNetworkValue);
}
}
@ -556,9 +584,17 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|| (isAssignment && matchingQueuedAssignment)) {
// this was either not a static assignment or it was and we had a matching one in the queue
QUuid nodeUUID;
// create a new session UUID for this node
QUuid nodeUUID = QUuid::createUuid();
if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) {
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
nodeUUID = packetUUID;
} else {
// we got a packetUUID we didn't recognize, just add the node
nodeUUID = QUuid::createUuid();
}
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr);
@ -675,6 +711,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
// if we've established a connection via ICE with this peer, use that socket
// otherwise just try to reply back to them on their sending socket (although that may not work)
HifiSockAddr destinationSockAddr = _connectedICEPeers.value(node->getUUID());
if (destinationSockAddr.isNull()) {
destinationSockAddr = senderSockAddr;
}
if (nodeInterestList.size() > 0) {
@ -912,18 +955,45 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) {
}
}
void DomainServer::requestCurrentIPAddressViaSTUN() {
void DomainServer::requestCurrentPublicSocketViaSTUN() {
LimitedNodeList::getInstance()->sendSTUNRequest();
}
void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr) {
QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address";
const QString SOCKET_PORT_KEY = "port";
QJsonObject socketObject;
socketObject[SOCKET_NETWORK_ADDRESS_KEY] = socket.getAddress().toString();
socketObject[SOCKET_PORT_KEY] = socket.getPort();
return socketObject;
}
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
updateNetworkingInfoWithDataServer(IP_ONLY_AUTOMATIC_NETWORKING_VALUE, newPublicSockAddr.getAddress().toString());
}
void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID();
// setup the domain object to send to the data server
const QString DOMAIN_JSON_OBJECT = "{\"domain\":{\"network_address\":\"%1\"}}";
const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString());
QJsonObject domainObject;
if (!networkAddress.isEmpty()) {
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
}
qDebug() << "Updating automatic networking setting in domain-server to" << newSetting;
domainObject[AUTOMATIC_NETWORKING_KEY] = newSetting;
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
QNetworkAccessManager::PutOperation,
@ -931,6 +1001,86 @@ void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublic
domainUpdateJSON.toUtf8());
}
// todo: have data-web respond with ice-server hostname to use
void DomainServer::performICEUpdates() {
sendHearbeatToIceServer();
sendICEPingPackets();
}
void DomainServer::sendHearbeatToIceServer() {
const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT);
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR);
}
void DomainServer::sendICEPingPackets() {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
QHash<QUuid, NetworkPeer>::iterator peer = _connectingICEPeers.begin();
while (peer != _connectingICEPeers.end()) {
if (peer->getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
// we've already tried to connect to this peer enough times
// remove it from our list - if it wants to re-connect it'll come back through ice-server
peer = _connectingICEPeers.erase(peer);
} else {
// send ping packets to this peer's interfaces
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
<< peer->getUUID();
// send the ping packet to the local and public sockets for this node
QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false);
nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket());
QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false);
nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket());
peer->incrementConnectionAttempts();
// go to next peer in hash
++peer;
}
}
}
void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) {
// loop through the packet and pull out network peers
// any peer we don't have we add to the hash, otherwise we update
QDataStream iceResponseStream(packet);
iceResponseStream.skipRawData(numBytesForPacketHeader(packet));
NetworkPeer receivedPeer;
while (!iceResponseStream.atEnd()) {
iceResponseStream >> receivedPeer;
if (!_connectedICEPeers.contains(receivedPeer.getUUID())) {
if (!_connectingICEPeers.contains(receivedPeer.getUUID())) {
qDebug() << "New peer requesting connection being added to hash -" << receivedPeer;
}
_connectingICEPeers[receivedPeer.getUUID()] = receivedPeer;
}
}
}
void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
QUuid nodeUUID = uuidFromPacketHeader(packet);
NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID);
if (!sendingPeer.isNull()) {
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
if (senderSockAddr == sendingPeer.getLocalSocket()) {
qDebug() << "Activating local socket for communication with network peer -" << sendingPeer;
_connectedICEPeers.insert(nodeUUID, sendingPeer.getLocalSocket());
} else if (senderSockAddr == sendingPeer.getPublicSocket()) {
qDebug() << "Activating public socket for communication with network peer -" << sendingPeer;
_connectedICEPeers.insert(nodeUUID, sendingPeer.getPublicSocket());
}
}
}
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
@ -972,6 +1122,19 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
case PacketTypeStunResponse:
nodeList->processSTUNResponse(receivedPacket);
break;
case PacketTypeUnverifiedPing: {
QByteArray pingReplyPacket = nodeList->constructPingReplyPacket(receivedPacket);
nodeList->writeUnverifiedDatagram(pingReplyPacket, senderSockAddr);
break;
}
case PacketTypeUnverifiedPingReply: {
processICEPingReply(receivedPacket, senderSockAddr);
break;
}
case PacketTypeIceServerHeartbeatResponse:
processICEHeartbeatResponse(receivedPacket);
break;
default:
break;
}
@ -1676,6 +1839,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
}
void DomainServer::nodeKilled(SharedNodePointer node) {
// remove this node from the connecting / connected ICE lists (if they exist)
_connectingICEPeers.remove(node->getUUID());
_connectedICEPeers.remove(node->getUUID());
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());

View file

@ -62,15 +62,22 @@ private slots:
void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer();
void requestCurrentIPAddressViaSTUN();
void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr);
void requestCurrentPublicSocketViaSTUN();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void performICEUpdates();
void sendHearbeatToIceServer();
void sendICEPingPackets();
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate();
bool hasOAuthProviderAndAuthInformation();
bool didSetupAccountManagerWithAccessToken();
bool optionallySetupAssignmentPayment();
void setupDynamicIPAddressUpdating();
void setupAutomaticNetworking();
void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString());
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
void processICEHeartbeatResponse(const QByteArray& packet);
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
@ -130,7 +137,13 @@ private:
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
HifiSockAddr _localSockAddr;
QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
DomainServerSettingsManager _settingsManager;
};
#endif // hifi_DomainServer_h

View file

@ -26,6 +26,10 @@
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_configMap()
@ -63,6 +67,36 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
}
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
if (foundValue) {
return *foundValue;
} else {
int dotIndex = keyPath.indexOf('.');
QString groupKey = keyPath.mid(0, dotIndex);
QString settingKey = keyPath.mid(dotIndex + 1);
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
QVariantMap groupMap = group.toMap();
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
QVariantMap settingMap = setting.toMap();
if (settingMap[DESCRIPTION_NAME_KEY].toString() == settingKey) {
return settingMap[SETTING_DEFAULT_KEY];
}
}
return QVariant();
}
}
}
return QVariant();
}
const QString SETTINGS_PATH = "/settings.json";
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
@ -127,10 +161,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
return false;
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
QJsonObject responseObject;

View file

@ -26,6 +26,7 @@ public:
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
private:

View file

@ -0,0 +1,9 @@
set(TARGET_NAME ice-server)
# setup the project and link required Qt modules
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(networking shared)
link_shared_dependencies()

View file

@ -0,0 +1,164 @@
//
// IceServer.cpp
// ice-server/src
//
// Created by Stephen Birarda on 2014-10-01.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QTimer>
#include <LimitedNodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include "IceServer.h"
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
IceServer::IceServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_id(QUuid::createUuid()),
_serverSocket(),
_activePeers()
{
// start the ice-server socket
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
// call our process datagrams slot when the UDP socket has packets ready
connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams);
// setup our timer to clear inactive peers
QTimer* inactivePeerTimer = new QTimer(this);
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
}
void IceServer::processDatagrams() {
HifiSockAddr sendingSockAddr;
QByteArray incomingPacket;
while (_serverSocket.hasPendingDatagrams()) {
incomingPacket.resize(_serverSocket.pendingDatagramSize());
_serverSocket.readDatagram(incomingPacket.data(), incomingPacket.size(),
sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer());
if (packetTypeForPacket(incomingPacket) == PacketTypeIceServerHeartbeat) {
QUuid senderUUID = uuidFromPacketHeader(incomingPacket);
// pull the public and private sock addrs for this peer
HifiSockAddr publicSocket, localSocket;
QDataStream hearbeatStream(incomingPacket);
hearbeatStream.skipRawData(numBytesForPacketHeader(incomingPacket));
hearbeatStream >> publicSocket >> localSocket;
// make sure we have this sender in our peer hash
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
if (!matchingPeer) {
// if we don't have this sender we need to create them now
matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket));
_activePeers.insert(senderUUID, matchingPeer);
qDebug() << "Added a new network peer" << *matchingPeer;
} else {
// we already had the peer so just potentially update their sockets
matchingPeer->setPublicSocket(publicSocket);
matchingPeer->setLocalSocket(localSocket);
qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer;
}
// update our last heard microstamp for this network peer to now
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
// check if this node also included a UUID that they would like to connect to
QUuid connectRequestID;
hearbeatStream >> connectRequestID;
// get the peers asking for connections with this peer
QSet<QUuid>& requestingConnections = _currentConnections[senderUUID];
if (!connectRequestID.isNull()) {
qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID);
// ensure this peer is in the set of current connections for the peer with ID it wants to connect with
_currentConnections[connectRequestID].insert(senderUUID);
// add the ID of the node they have said they would like to connect to
requestingConnections.insert(connectRequestID);
}
if (requestingConnections.size() > 0) {
// send a heartbeart response based on the set of connections
qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size()
<< "potential connections";
sendHeartbeatResponse(sendingSockAddr, requestingConnections);
}
}
}
}
void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet<QUuid>& connections) {
QSet<QUuid>::iterator peerID = connections.begin();
QByteArray outgoingPacket(MAX_PACKET_SIZE, 0);
int currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id);
// go through the connections, sending packets containing connection information for those nodes
while (peerID != connections.end()) {
SharedNetworkPeer matchingPeer = _activePeers.value(*peerID);
// if this node is inactive we remove it from the set
if (!matchingPeer) {
peerID = connections.erase(peerID);
} else {
// get the byte array for this peer
QByteArray peerBytes = matchingPeer->toByteArray();
if (currentPacketSize + peerBytes.size() > MAX_PACKET_SIZE) {
// write the current packet
_serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize,
destinationSockAddr.getAddress(), destinationSockAddr.getPort());
// reset the packet size to our number of header bytes
currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id);
}
// append the current peer bytes
outgoingPacket.insert(currentPacketSize, peerBytes);
currentPacketSize += peerBytes.size();
++peerID;
}
}
// write the last packet
_serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize,
destinationSockAddr.getAddress(), destinationSockAddr.getPort());
}
void IceServer::clearInactivePeers() {
NetworkPeerHash::iterator peerItem = _activePeers.begin();
while (peerItem != _activePeers.end()) {
SharedNetworkPeer peer = peerItem.value();
if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) {
qDebug() << "Removing peer from memory for inactivity -" << *peer;
peerItem = _activePeers.erase(peerItem);
} else {
// we didn't kill this peer, push the iterator forwards
++peerItem;
}
}
}

View file

@ -0,0 +1,39 @@
//
// IceServer.h
// ice-server/src
//
// Created by Stephen Birarda on 2014-10-01.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_IceServer_h
#define hifi_IceServer_h
#include <qcoreapplication.h>
#include <qsharedpointer.h>
#include <qudpsocket.h>
#include <NetworkPeer.h>
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
class IceServer : public QCoreApplication {
public:
IceServer(int argc, char* argv[]);
private slots:
void processDatagrams();
void clearInactivePeers();
private:
void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet<QUuid>& connections);
QUuid _id;
QUdpSocket _serverSocket;
NetworkPeerHash _activePeers;
QHash<QUuid, QSet<QUuid> > _currentConnections;
};
#endif // hifi_IceServer_h

27
ice-server/src/main.cpp Normal file
View file

@ -0,0 +1,27 @@
//
// main.cpp
// ice-server/src
//
// Created by Stephen Birarda on 10/01/12.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QCoreApplication>
#include <Logging.h>
#include "IceServer.h"
int main(int argc, char* argv[]) {
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
qInstallMessageHandler(Logging::verboseMessageHandler);
IceServer iceServer(argc, argv);
return iceServer.exec();
}

View file

@ -299,9 +299,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
AddressManager& addressManager = AddressManager::getInstance();
// connect to the domainChangeRequired signal on AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequired,
// use our MyAvatar position and quat for address manager path
addressManager.setPositionGetter(getPositionForPath);
addressManager.setOrientationGetter(getOrientationForPath);
// handle domain change signals from AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredToHostname,
this, &Application::changeDomainHostname);
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredViaICEForID,
&domainHandler, &DomainHandler::setIceServerHostnameAndID);
_settings = new QSettings(this);
_numChangedSettings = 0;
@ -1792,16 +1799,25 @@ void Application::init() {
Menu::getInstance()->loadSettings();
_audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings());
qDebug() << "Loaded settings";
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
if (urlIndex != -1) {
AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1));
} else {
// check if we have a URL in settings to load to jump back to
// we load this separate from the other settings so we don't double lookup a URL
QSettings* interfaceSettings = lockSettings();
QUrl addressURL = interfaceSettings->value(SETTINGS_ADDRESS_KEY).toUrl();
AddressManager::getInstance().handleLookupString(addressURL.toString());
unlockSettings();
}
qDebug() << "Loaded settings";
#ifdef __APPLE__
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) {
// on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash
@ -3424,7 +3440,7 @@ void Application::updateWindowTitle(){
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) ";
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
+ nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion;
+ AddressManager::getInstance().getCurrentDomain() + connectionStatus + buildVersion;
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.getAccountInfo().hasBalance()) {
@ -3455,9 +3471,7 @@ void Application::updateLocationInServer() {
QJsonObject locationObject;
QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(),
true,
_myAvatar->getOrientation());
QString pathString = AddressManager::getInstance().currentPath();
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";

View file

@ -140,6 +140,8 @@ class Application : public QApplication {
public:
static Application* getInstance() { return static_cast<Application*>(QCoreApplication::instance()); }
static QString& resourcesPath();
static const glm::vec3& getPositionForPath() { return getInstance()->_myAvatar->getPosition(); }
static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); }
Application(int& argc, char** argv, QElapsedTimer &startup_time);
~Application();

View file

@ -754,7 +754,6 @@ void Menu::loadSettings(QSettings* settings) {
scanMenuBar(&loadAction, settings);
Application::getInstance()->getAvatar()->loadData(settings);
Application::getInstance()->updateWindowTitle();
NodeList::getInstance()->loadData(settings);
// notify that a settings has changed
connect(&NodeList::getInstance()->getDomainHandler(), &DomainHandler::hostnameChanged, this, &Menu::bumpSettings);
@ -815,7 +814,8 @@ void Menu::saveSettings(QSettings* settings) {
scanMenuBar(&saveAction, settings);
Application::getInstance()->getAvatar()->saveData(settings);
NodeList::getInstance()->saveData(settings);
settings->setValue(SETTINGS_ADDRESS_KEY, AddressManager::getInstance().currentAddress());
if (lockedSettings) {
Application::getInstance()->unlockSettings();

View file

@ -51,6 +51,8 @@ const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f;
const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f;
const QString SETTINGS_ADDRESS_KEY = "address";
enum FrustumDrawMode {
FRUSTUM_DRAW_MODE_ALL,
FRUSTUM_DRAW_MODE_VECTORS,

View file

@ -1551,6 +1551,11 @@ public:
AxisIndex(int x = -1, int y = -1, int z = -1) : x(x), y(y), z(z) { }
};
static glm::vec3 safeNormalize(const glm::vec3& vector) {
float length = glm::length(vector);
return (length > 0.0f) ? (vector / length) : vector;
}
int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
if (!info.isLeaf) {
return DEFAULT_ORDER;
@ -1879,7 +1884,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
}
}
}
glm::vec3 normal = glm::normalize(axisNormals[0] + axisNormals[1] + axisNormals[2]);
glm::vec3 normal = safeNormalize(axisNormals[0] + axisNormals[1] + axisNormals[2]);
center /= crossingCount;
// use a sequence of Givens rotations to perform a QR decomposition
@ -1967,12 +1972,12 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
vertices.append(point);
} else {
axisNormals[0] = glm::normalize(axisNormals[0]);
axisNormals[1] = glm::normalize(axisNormals[1]);
axisNormals[2] = glm::normalize(axisNormals[2]);
glm::vec3 normalXY(glm::normalize(axisNormals[0] + axisNormals[1]));
glm::vec3 normalXZ(glm::normalize(axisNormals[0] + axisNormals[2]));
glm::vec3 normalYZ(glm::normalize(axisNormals[1] + axisNormals[2]));
axisNormals[0] = safeNormalize(axisNormals[0]);
axisNormals[1] = safeNormalize(axisNormals[1]);
axisNormals[2] = safeNormalize(axisNormals[2]);
glm::vec3 normalXY(safeNormalize(axisNormals[0] + axisNormals[1]));
glm::vec3 normalXZ(safeNormalize(axisNormals[0] + axisNormals[2]));
glm::vec3 normalYZ(safeNormalize(axisNormals[1] + axisNormals[2]));
if (glm::dot(axisNormals[0], normalXY) > CREASE_COS_NORMAL &&
glm::dot(axisNormals[1], normalXY) > CREASE_COS_NORMAL) {
point.setNormal(normalXY);

View file

@ -740,16 +740,8 @@ AnimationDetails MyAvatar::getAnimationDetails(const QString& url) {
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
settings->setValue("bodyYaw", _bodyYaw);
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
settings->setValue("headPitch", getHead()->getBasePitch());
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
settings->setValue("position_z", _position.z);
settings->setValue("pupilDilation", getHead()->getPupilDilation());
settings->setValue("leanScale", _leanScale);
@ -800,19 +792,8 @@ void MyAvatar::saveData(QSettings* settings) {
void MyAvatar::loadData(QSettings* settings) {
settings->beginGroup("Avatar");
// in case settings is corrupt or missing loadSetting() will check for NaN
_bodyYaw = loadSetting(settings, "bodyYaw", 0.0f);
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
glm::vec3 newPosition;
newPosition.x = loadSetting(settings, "position_x", START_LOCATION.x);
newPosition.y = loadSetting(settings, "position_y", START_LOCATION.y);
newPosition.z = loadSetting(settings, "position_z", START_LOCATION.z);
slamPosition(newPosition);
getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f));
_leanScale = loadSetting(settings, "leanScale", 0.05f);

View file

@ -101,7 +101,7 @@ void SixenseManager::initialize() {
_sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME);
#else
const QString SIXENSE_LIBRARY_NAME = "libsixense_x64";
QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "../Frameworks/"
QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/"
+ SIXENSE_LIBRARY_NAME;
_sixenseLibrary = new QLibrary(frameworkSixenseLibrary);

View file

@ -29,9 +29,7 @@ QString LocationScriptingInterface::getHref() {
}
QString LocationScriptingInterface::getPathname() {
MyAvatar* applicationAvatar = Application::getInstance()->getAvatar();
return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(),
true, applicationAvatar->getOrientation());
return AddressManager::getInstance().currentPath();
}
QString LocationScriptingInterface::getHostname() {

View file

@ -1,190 +0,0 @@
JENKINS_URL = 'https://jenkins.below92.com/'
GITHUB_HOOK_URL = 'https://github.com/worklist/hifi/'
GIT_REPO_URL = 'git@github.com:worklist/hifi.git'
HIPCHAT_ROOM = 'High Fidelity'
def hifiJob(String targetName, Boolean deploy) {
job {
name "hifi-${targetName}"
logRotator(7, -1, -1, -1)
scm {
git(GIT_REPO_URL, 'master') { node ->
node << {
includedRegions "${targetName}/.*\nlibraries/.*"
useShallowClone true
}
}
}
configure { project ->
project / 'properties' << {
'com.coravy.hudson.plugins.github.GithubProjectProperty' {
projectUrl GITHUB_HOOK_URL
}
'jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty' {
room HIPCHAT_ROOM
}
'hudson.plugins.buildblocker.BuildBlockerProperty' {
useBuildBlocker true
blockingJobs 'hifi--seed'
}
}
project / 'triggers' << 'com.cloudbees.jenkins.GitHubPushTrigger' {
spec ''
}
}
configure cmakeBuild(targetName, 'make install')
if (deploy) {
publishers {
publishScp("${ARTIFACT_DESTINATION}") {
entry("**/build/${targetName}/${targetName}", "deploy/${targetName}")
}
}
}
configure { project ->
project / 'publishers' << {
if (deploy) {
'hudson.plugins.postbuildtask.PostbuildTask' {
'tasks' {
'hudson.plugins.postbuildtask.TaskProperties' {
logTexts {
'hudson.plugins.postbuildtask.LogProperties' {
logText '.'
operator 'AND'
}
}
EscalateStatus true
RunIfJobSuccessful true
script "curl -d 'action=deploy&role=highfidelity-live&revision=${targetName}' https://${ARTIFACT_DESTINATION}"
}
}
}
}
'jenkins.plugins.hipchat.HipChatNotifier' {
jenkinsUrl JENKINS_URL
authToken "${HIPCHAT_AUTH_TOKEN}"
room HIPCHAT_ROOM
}
}
}
}
}
static Closure cmakeBuild(srcDir, instCommand) {
return { project ->
project / 'builders' / 'hudson.plugins.cmake.CmakeBuilder' {
sourceDir '.'
buildDir 'build'
installDir ''
buildType 'RelWithDebInfo'
generator 'Unix Makefiles'
makeCommand "make ${srcDir}"
installCommand instCommand
preloadScript ''
cmakeArgs ''
projectCmakePath '/usr/local/bin/cmake'
cleanBuild 'false'
cleanInstallDir 'false'
builderImpl ''
}
}
}
def targets = [
'animation-server':true,
'assignment-server':true,
'assignment-client':true,
'domain-server':true,
'eve':true,
'pairing-server':true,
'space-server':true,
'voxel-server':true,
]
/* setup all of the target jobs to use the above template */
for (target in targets) {
queue hifiJob(target.key, target.value)
}
/* setup the OS X interface builds */
interfaceOSXJob = hifiJob('interface', false)
interfaceOSXJob.with {
name 'hifi-interface-osx'
scm {
git(GIT_REPO_URL, 'stable') { node ->
node << {
includedRegions "interface/.*\nlibraries/.*"
useShallowClone true
}
}
}
configure { project ->
project << {
assignedNode 'interface-mini'
canRoam false
}
}
}
queue interfaceOSXJob
/* setup the parametrized build job for builds from jenkins */
parameterizedJob = hifiJob('$TARGET', true)
parameterizedJob.with {
name 'hifi-branch-deploy'
parameters {
stringParam('GITHUB_USER', '', "Specifies the name of the GitHub user that we're building from.")
stringParam('GIT_BRANCH', '', "Specifies the specific branch to build and deploy.")
stringParam('HOSTNAME', 'devel.highfidelity.io', "Specifies the hostname to deploy against.")
stringParam('TARGET', '', "What server to build specifically")
}
scm {
git('git@github.com:/$GITHUB_USER/hifi.git', '$GIT_BRANCH') { node ->
node << {
wipeOutWorkspace true
useShallowClone true
}
}
}
configure { project ->
def curlCommand = 'curl -d action=hifidevgrid -d "hostname=$HOSTNAME" ' +
'-d "github_user=$GITHUB_USER" -d "build_branch=$GIT_BRANCH" ' +
"-d \"revision=\$TARGET\" https://${ARTIFACT_DESTINATION}"
(project / publishers / 'hudson.plugins.postbuildtask.PostbuildTask' /
tasks / 'hudson.plugins.postbuildtask.TaskProperties' / script).setValue(curlCommand)
}
}
doxygenJob = hifiJob('docs', false)
doxygenJob.with {
scm {
git(GIT_REPO_URL, 'master') { node ->
node << {
useShallowClone true
}
}
}
configure { project ->
(project / builders).setValue('')
}
steps {
shell('doxygen')
}
}
queue doxygenJob

View file

@ -59,7 +59,8 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
AccountManager::AccountManager() :
_authURL(),
_pendingCallbackMap(),
_accountInfo()
_accountInfo(),
_shouldPersistToSettingsFile(true)
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
@ -83,14 +84,18 @@ void AccountManager::logout() {
emit balanceChanged(0);
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
if (_shouldPersistToSettingsFile) {
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
} else {
qDebug() << "Cleared data server account info in account manager.";
}
emit logoutComplete();
// the username has changed to blank
@ -116,28 +121,29 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString());
qDebug() << "Re-setting authentication flow.";
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
qDebug() << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
if (_shouldPersistToSettingsFile) {
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
}
}
}
}
@ -314,8 +320,9 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString());
}
@ -337,6 +344,19 @@ bool AccountManager::checkAndSignalForAccessToken() {
return hasToken;
}
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
// clear our current DataServerAccountInfo
_accountInfo = DataServerAccountInfo();
// start the new account info with a new OAuthAccessToken
OAuthAccessToken newOAuthToken;
newOAuthToken.token = accessToken;
qDebug() << "Setting new account manager access token to" << accessToken;
_accountInfo.setAccessToken(newOAuthToken);
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
@ -387,12 +407,14 @@ void AccountManager::requestAccessTokenFinished() {
_accountInfo.setAccessTokenFromJSON(rootObject);
emit loginComplete(rootURL);
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
requestProfile();
}
@ -436,13 +458,16 @@ void AccountManager::requestProfileFinished() {
// the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername());
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
} else {
// TODO: error handling
qDebug() << "Error in response for profile";

View file

@ -59,10 +59,13 @@ public:
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
bool hasValidAccessToken();
Q_INVOKABLE bool checkAndSignalForAccessToken();
void setAccessTokenForCurrentAuthURL(const QString& accessToken);
void requestAccessToken(const QString& login, const QString& password);
void requestProfile();
@ -107,6 +110,7 @@ private:
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
DataServerAccountInfo _accountInfo;
bool _shouldPersistToSettingsFile;
};
#endif // hifi_AccountManager_h

View file

@ -22,17 +22,46 @@ AddressManager& AddressManager::getInstance() {
return sharedInstance;
}
QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation,
const glm::quat& orientation) {
AddressManager::AddressManager() :
_currentDomain(),
_positionGetter(NULL),
_orientationGetter(NULL)
{
QString pathString = "/" + createByteArray(position);
}
const QUrl AddressManager::currentAddress() {
QUrl hifiURL;
if (hasOrientation) {
QString orientationString = createByteArray(orientation);
pathString += "/" + orientationString;
hifiURL.setScheme(HIFI_URL_SCHEME);
hifiURL.setHost(_currentDomain);
hifiURL.setPath(currentPath());
return hifiURL;
}
const QString AddressManager::currentPath(bool withOrientation) const {
if (_positionGetter) {
QString pathString = "/" + createByteArray(_positionGetter());
if (withOrientation) {
if (_orientationGetter) {
QString orientationString = createByteArray(_orientationGetter());
pathString += "/" + orientationString;
} else {
qDebug() << "Cannot add orientation to path without a getter for position."
<< "Call AdressManager::setOrientationGetter to pass a function that will return a glm::quat";
}
}
return pathString;
} else {
qDebug() << "Cannot create address path without a getter for position."
<< "Call AdressManager::setPositionGetter to pass a function that will return a const glm::vec3&";
return QString();
}
return pathString;
}
const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
@ -118,9 +147,26 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
QJsonObject domainObject = dataObject[ADDRESS_API_DOMAIN_KEY].toObject();
const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address";
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address";
emit possibleDomainChangeRequired(domainHostname);
if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) {
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
emit possibleDomainChangeRequiredToHostname(domainHostname);
} else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
const QString DOMAIN_ID_KEY = "id";
QString domainIDString = domainObject[DOMAIN_ID_KEY].toString();
QUuid domainID(domainIDString);
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
}
// set our current domain to the name that came back
const QString DOMAIN_NAME_KEY = "name";
_currentDomain = domainObject[DOMAIN_NAME_KEY].toString();
// take the path that came back
const QString LOCATION_KEY = "location";
@ -132,7 +178,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
} else if (domainObject.contains(LOCATION_KEY)) {
returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString();
}
bool shouldFaceViewpoint = dataObject.contains(ADDRESS_API_ONLINE_KEY);
if (!returnedPath.isEmpty()) {
@ -182,16 +228,25 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive);
if (hostnameRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
QString domainHostname = hostnameRegex.cap(0);
emit possibleDomainChangeRequiredToHostname(domainHostname);
emit lookupResultsFinished();
_currentDomain = domainHostname;
return true;
}
QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING);
if (ipAddressRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
QString domainIPString = ipAddressRegex.cap(0);
emit possibleDomainChangeRequiredToHostname(domainIPString);
emit lookupResultsFinished();
_currentDomain = domainIPString;
return true;
}

View file

@ -21,15 +21,24 @@
static const QString HIFI_URL_SCHEME = "hifi";
typedef const glm::vec3& (*PositionGetter)();
typedef glm::quat (*OrientationGetter)();
class AddressManager : public QObject {
Q_OBJECT
public:
static AddressManager& getInstance();
static QString pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation = false,
const glm::quat& orientation = glm::quat());
const QUrl currentAddress();
const QString currentPath(bool withOrientation = true) const;
const QString& getCurrentDomain() const { return _currentDomain; }
void attemptPlaceNameLookup(const QString& lookupString);
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
public slots:
void handleLookupString(const QString& lookupString);
@ -40,11 +49,14 @@ signals:
void lookupResultsFinished();
void lookupResultIsOffline();
void lookupResultIsNotFound();
void possibleDomainChangeRequired(const QString& newHostname);
void possibleDomainChangeRequiredToHostname(const QString& newHostname);
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
void locationChangeRequired(const glm::vec3& newPosition,
bool hasOrientationChange, const glm::quat& newOrientation,
bool shouldFaceLocation);
private:
AddressManager();
const JSONCallbackParameters& apiCallbackParameters();
bool handleUrl(const QUrl& lookupUrl);
@ -52,6 +64,10 @@ private:
bool handleNetworkAddress(const QString& lookupString);
bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false);
bool handleUsername(const QString& lookupString);
QString _currentDomain;
PositionGetter _positionGetter;
OrientationGetter _orientationGetter;
};
#endif // hifi_AddressManager_h

View file

@ -27,6 +27,7 @@ public:
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
const OAuthAccessToken& getAccessToken() const { return _accessToken; }
void setAccessToken(const OAuthAccessToken& accessToken) { _accessToken = accessToken; }
void setAccessTokenFromJSON(const QJsonObject& jsonObject);
const QString& getUsername() const { return _username; }

View file

@ -25,6 +25,9 @@ DomainHandler::DomainHandler(QObject* parent) :
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_iceClientID(),
_iceServerSockAddr(),
_icePeer(),
_isConnected(false),
_handshakeTimer(NULL),
_settingsObject(),
@ -35,7 +38,12 @@ DomainHandler::DomainHandler(QObject* parent) :
void DomainHandler::clearConnectionInfo() {
_uuid = QUuid();
_iceServerSockAddr = HifiSockAddr();
_icePeer = NetworkPeer();
_isConnected = false;
emit disconnectedFromDomain();
if (_handshakeTimer) {
@ -123,6 +131,31 @@ void DomainHandler::setHostname(const QString& hostname) {
}
}
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
if (id != _uuid) {
// re-set the domain info to connect to new domain
hardReset();
setUUID(id);
_iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
// refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid();
qDebug() << "ICE required to connect to domain via ice server at" << iceServerHostname;
}
}
void DomainHandler::activateICELocalSocket() {
_sockAddr = _icePeer.getLocalSocket();
_hostname = _sockAddr.getAddress().toString();
}
void DomainHandler::activateICEPublicSocket() {
_sockAddr = _icePeer.getPublicSocket();
_hostname = _sockAddr.getAddress().toString();
}
void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
for (int i = 0; i < hostInfo.addresses().size(); i++) {
if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
@ -152,21 +185,29 @@ void DomainHandler::setIsConnected(bool isConnected) {
}
}
void DomainHandler::requestDomainSettings() const {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
void DomainHandler::requestDomainSettings() {
NodeType_t owningNodeType = NodeList::getInstance()->getOwnerType();
if (owningNodeType == NodeType::Agent) {
// for now the agent nodes don't need any settings - this allows local assignment-clients
// to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet)
_settingsObject = QJsonObject();
emit settingsReceived(_settingsObject);
} else {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
}
}
}
@ -216,3 +257,20 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement
// initializeDTLSSession();
}
void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) {
QDataStream iceResponseStream(icePacket);
iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket));
NetworkPeer packetPeer;
iceResponseStream >> packetPeer;
if (packetPeer.getUUID() != _uuid) {
qDebug() << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
} else {
qDebug() << "Received network peer object for domain -" << packetPeer;
_icePeer = packetPeer;
emit requestICEConnectionAttempt();
}
}

View file

@ -20,6 +20,7 @@
#include <QtNetwork/QHostInfo>
#include "HifiSockAddr.h"
#include "NetworkPeer.h"
const QString DEFAULT_DOMAIN_HOSTNAME = "sandbox.highfidelity.io";
@ -54,18 +55,28 @@ public:
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getICEClientID() const { return _iceClientID; }
bool requiresICE() const { return !_iceServerSockAddr.isNull(); }
const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; }
NetworkPeer& getICEPeer() { return _icePeer; }
void activateICELocalSocket();
void activateICEPublicSocket();
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
bool hasSettings() const { return !_settingsObject.isEmpty(); }
void requestDomainSettings() const;
void requestDomainSettings();
const QJsonObject& getSettingsObject() const { return _settingsObject; }
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
void processICEResponsePacket(const QByteArray& icePacket);
void softReset();
public slots:
void setHostname(const QString& hostname);
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
@ -74,6 +85,7 @@ signals:
void hostnameChanged(const QString& hostname);
void connectedToDomain(const QString& hostname);
void disconnectedFromDomain();
void requestICEConnectionAttempt();
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
@ -85,6 +97,9 @@ private:
QString _hostname;
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
QUuid _iceClientID;
HifiSockAddr _iceServerSockAddr;
NetworkPeer _icePeer;
bool _isConnected;
QTimer* _handshakeTimer;
QJsonObject _settingsObject;

View file

@ -459,6 +459,39 @@ unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeS
return n;
}
QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVerified, const QUuid& packetHeaderID) {
QByteArray pingPacket = byteArrayWithPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing,
packetHeaderID);
QDataStream packetStream(&pingPacket, QIODevice::Append);
packetStream << pingType;
packetStream << usecTimestampNow();
return pingPacket;
}
QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID) {
QDataStream pingPacketStream(pingPacket);
pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket));
PingType_t typeFromOriginalPing;
pingPacketStream >> typeFromOriginalPing;
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
PacketType replyType = (packetTypeForPacket(pingPacket) == PacketTypePing)
? PacketTypePingReply : PacketTypeUnverifiedPingReply;
QByteArray replyPacket = byteArrayWithPopulatedHeader(replyType, packetHeaderID);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();
return replyPacket;
}
SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) {
if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) {
@ -618,3 +651,25 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) {
return false;
}
void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID, const QUuid& connectionRequestID) {
if (headerID.isNull()) {
headerID = _sessionUUID;
}
QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, headerID);
QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append);
iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort());
if (!connectionRequestID.isNull()) {
iceDataStream << connectionRequestID;
qDebug() << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(connectionRequestID);
}
writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr);
}

View file

@ -52,6 +52,14 @@ typedef QSharedPointer<Node> SharedNodePointer;
typedef QHash<QUuid, SharedNodePointer> NodeHash;
Q_DECLARE_METATYPE(SharedNodePointer)
typedef quint8 PingType_t;
namespace PingType {
const PingType_t Agnostic = 0;
const PingType_t Local = 1;
const PingType_t Public = 2;
const PingType_t Symmetric = 3;
}
class LimitedNodeList : public QObject {
Q_OBJECT
public:
@ -104,8 +112,15 @@ public:
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void resetPacketStats();
QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true,
const QUuid& packetHeaderID = QUuid());
QByteArray constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID = QUuid());
virtual void sendSTUNRequest();
virtual bool processSTUNResponse(const QByteArray& packet);
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid());
public slots:
void reset();
void eraseAllNodes();

View file

@ -0,0 +1,99 @@
//
// NetworkPeer.cpp
// libraries/networking/src
//
// Created by Stephen Birarda on 2014-10-02.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <qdatetime.h>
#include <SharedUtil.h>
#include <UUID.h>
#include "NetworkPeer.h"
NetworkPeer::NetworkPeer() :
_uuid(),
_publicSocket(),
_localSocket(),
_wakeTimestamp(QDateTime::currentMSecsSinceEpoch()),
_lastHeardMicrostamp(usecTimestampNow()),
_connectionAttempts(0)
{
}
NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) :
_uuid(uuid),
_publicSocket(publicSocket),
_localSocket(localSocket),
_wakeTimestamp(QDateTime::currentMSecsSinceEpoch()),
_lastHeardMicrostamp(usecTimestampNow()),
_connectionAttempts(0)
{
}
NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) {
_uuid = otherPeer._uuid;
_publicSocket = otherPeer._publicSocket;
_localSocket = otherPeer._localSocket;
_wakeTimestamp = otherPeer._wakeTimestamp;
_lastHeardMicrostamp = otherPeer._lastHeardMicrostamp;
_connectionAttempts = otherPeer._connectionAttempts;
}
NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) {
NetworkPeer temp(otherPeer);
swap(temp);
return *this;
}
void NetworkPeer::swap(NetworkPeer& otherPeer) {
using std::swap;
swap(_uuid, otherPeer._uuid);
swap(_publicSocket, otherPeer._publicSocket);
swap(_localSocket, otherPeer._localSocket);
swap(_wakeTimestamp, otherPeer._wakeTimestamp);
swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp);
swap(_connectionAttempts, otherPeer._connectionAttempts);
}
QByteArray NetworkPeer::toByteArray() const {
QByteArray peerByteArray;
QDataStream peerStream(&peerByteArray, QIODevice::Append);
peerStream << *this;
return peerByteArray;
}
QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) {
out << peer._uuid;
out << peer._publicSocket;
out << peer._localSocket;
return out;
}
QDataStream& operator>>(QDataStream& in, NetworkPeer& peer) {
in >> peer._uuid;
in >> peer._publicSocket;
in >> peer._localSocket;
return in;
}
QDebug operator<<(QDebug debug, const NetworkPeer &peer) {
debug << uuidStringWithoutCurlyBraces(peer.getUUID())
<< "- public:" << peer.getPublicSocket()
<< "- local:" << peer.getLocalSocket();
return debug;
}

View file

@ -0,0 +1,77 @@
//
// NetworkPeer.h
// libraries/networking/src
//
// Created by Stephen Birarda on 2014-10-02.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_NetworkPeer_h
#define hifi_NetworkPeer_h
#include <qobject.h>
#include <quuid.h>
#include "HifiSockAddr.h"
const QString ICE_SERVER_HOSTNAME = "localhost";
const int ICE_SERVER_DEFAULT_PORT = 7337;
const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000;
const int MAX_ICE_CONNECTION_ATTEMPTS = 5;
class NetworkPeer : public QObject {
public:
NetworkPeer();
NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
// privatize copy and assignment operator to disallow peer copying
NetworkPeer(const NetworkPeer &otherPeer);
NetworkPeer& operator=(const NetworkPeer& otherPeer);
bool isNull() const { return _uuid.isNull(); }
const QUuid& getUUID() const { return _uuid; }
void setUUID(const QUuid& uuid) { _uuid = uuid; }
void reset();
const HifiSockAddr& getPublicSocket() const { return _publicSocket; }
virtual void setPublicSocket(const HifiSockAddr& publicSocket) { _publicSocket = publicSocket; }
const HifiSockAddr& getLocalSocket() const { return _localSocket; }
virtual void setLocalSocket(const HifiSockAddr& localSocket) { _localSocket = localSocket; }
quint64 getWakeTimestamp() const { return _wakeTimestamp; }
void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; }
quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; }
void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; }
QByteArray toByteArray() const;
int getConnectionAttempts() const { return _connectionAttempts; }
void incrementConnectionAttempts() { ++_connectionAttempts; }
void resetConnectionAttemps() { _connectionAttempts = 0; }
friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer);
friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer);
protected:
QUuid _uuid;
HifiSockAddr _publicSocket;
HifiSockAddr _localSocket;
quint64 _wakeTimestamp;
quint64 _lastHeardMicrostamp;
int _connectionAttempts;
private:
void swap(NetworkPeer& otherPeer);
};
QDebug operator<<(QDebug debug, const NetworkPeer &peer);
typedef QSharedPointer<NetworkPeer> SharedNetworkPeer;
#endif // hifi_NetworkPeer_h

View file

@ -12,11 +12,12 @@
#include <cstring>
#include <stdio.h>
#include <UUID.h>
#include "Node.h"
#include "SharedUtil.h"
#include <QtCore/QDataStream>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
const QString UNKNOWN_NodeType_t_NAME = "Unknown";
@ -44,14 +45,10 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
}
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) :
NetworkPeer(uuid, publicSocket, localSocket),
_type(type),
_uuid(uuid),
_wakeTimestamp(QDateTime::currentMSecsSinceEpoch()),
_lastHeardMicrostamp(usecTimestampNow()),
_publicSocket(publicSocket),
_localSocket(localSocket),
_symmetricSocket(),
_activeSocket(NULL),
_symmetricSocket(),
_connectionSecret(),
_bytesReceivedMovingAverage(NULL),
_linkedData(NULL),
@ -68,48 +65,6 @@ Node::~Node() {
delete _bytesReceivedMovingAverage;
}
void Node::setPublicSocket(const HifiSockAddr& publicSocket) {
if (_activeSocket == &_publicSocket) {
// if the active socket was the public socket then reset it to NULL
_activeSocket = NULL;
}
_publicSocket = publicSocket;
}
void Node::setLocalSocket(const HifiSockAddr& localSocket) {
if (_activeSocket == &_localSocket) {
// if the active socket was the local socket then reset it to NULL
_activeSocket = NULL;
}
_localSocket = localSocket;
}
void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) {
if (_activeSocket == &_symmetricSocket) {
// if the active socket was the symmetric socket then reset it to NULL
_activeSocket = NULL;
}
_symmetricSocket = symmetricSocket;
}
void Node::activateLocalSocket() {
qDebug() << "Activating local socket for node" << *this;
_activeSocket = &_localSocket;
}
void Node::activatePublicSocket() {
qDebug() << "Activating public socket for node" << *this;
_activeSocket = &_publicSocket;
}
void Node::activateSymmetricSocket() {
qDebug() << "Activating symmetric socket for node" << *this;
_activeSocket = &_symmetricSocket;
}
void Node::recordBytesReceived(int bytesReceived) {
if (!_bytesReceivedMovingAverage) {
_bytesReceivedMovingAverage = new SimpleMovingAverage(100);
@ -139,6 +94,48 @@ void Node::updateClockSkewUsec(int clockSkewSample) {
_clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile();
}
void Node::setPublicSocket(const HifiSockAddr& publicSocket) {
if (_activeSocket == &_publicSocket) {
// if the active socket was the public socket then reset it to NULL
_activeSocket = NULL;
}
_publicSocket = publicSocket;
}
void Node::setLocalSocket(const HifiSockAddr& localSocket) {
if (_activeSocket == &_localSocket) {
// if the active socket was the local socket then reset it to NULL
_activeSocket = NULL;
}
_localSocket = localSocket;
}
void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) {
if (_activeSocket == &_symmetricSocket) {
// if the active socket was the symmetric socket then reset it to NULL
_activeSocket = NULL;
}
_symmetricSocket = symmetricSocket;
}
void Node::activateLocalSocket() {
qDebug() << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid);
_activeSocket = &_localSocket;
}
void Node::activatePublicSocket() {
qDebug() << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid);
_activeSocket = &_publicSocket;
}
void Node::activateSymmetricSocket() {
qDebug() << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid);
_activeSocket = &_symmetricSocket;
}
QDataStream& operator<<(QDataStream& out, const Node& node) {
out << node._type;
out << node._uuid;

View file

@ -21,6 +21,7 @@
#include <QMutex>
#include "HifiSockAddr.h"
#include "NetworkPeer.h"
#include "NodeData.h"
#include "SimpleMovingAverage.h"
#include "MovingPercentile.h"
@ -44,7 +45,7 @@ namespace NodeType {
const QString& getNodeTypeName(NodeType_t nodeType);
}
class Node : public QObject {
class Node : public NetworkPeer {
Q_OBJECT
public:
Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
@ -55,28 +56,6 @@ public:
char getType() const { return _type; }
void setType(char type) { _type = type; }
const QUuid& getUUID() const { return _uuid; }
void setUUID(const QUuid& uuid) { _uuid = uuid; }
quint64 getWakeTimestamp() const { return _wakeTimestamp; }
void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; }
quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; }
void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; }
const HifiSockAddr& getPublicSocket() const { return _publicSocket; }
void setPublicSocket(const HifiSockAddr& publicSocket);
const HifiSockAddr& getLocalSocket() const { return _localSocket; }
void setLocalSocket(const HifiSockAddr& localSocket);
const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; }
void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
void activatePublicSocket();
void activateLocalSocket();
void activateSymmetricSocket();
const QUuid& getConnectionSecret() const { return _connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
@ -98,6 +77,17 @@ public:
void updateClockSkewUsec(int clockSkewSample);
QMutex& getMutex() { return _mutex; }
virtual void setPublicSocket(const HifiSockAddr& publicSocket);
virtual void setLocalSocket(const HifiSockAddr& localSocket);
const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; }
virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket);
const HifiSockAddr* getActiveSocket() const { return _activeSocket; }
void activatePublicSocket();
void activateLocalSocket();
void activateSymmetricSocket();
friend QDataStream& operator<<(QDataStream& out, const Node& node);
friend QDataStream& operator>>(QDataStream& in, Node& node);
@ -107,13 +97,10 @@ private:
Node& operator=(Node otherNode);
NodeType_t _type;
QUuid _uuid;
quint64 _wakeTimestamp;
quint64 _lastHeardMicrostamp;
HifiSockAddr _publicSocket;
HifiSockAddr _localSocket;
HifiSockAddr _symmetricSocket;
HifiSockAddr* _activeSocket;
HifiSockAddr _symmetricSocket;
QUuid _connectionSecret;
SimpleMovingAverage* _bytesReceivedMovingAverage;
NodeData* _linkedData;

View file

@ -64,6 +64,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
// clear our NodeList when the domain changes
connect(&_domainHandler, &DomainHandler::hostnameChanged, this, &NodeList::reset);
// handle ICE signal from DS so connection is attempted immediately
connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer);
// clear our NodeList when logout is requested
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
}
@ -123,6 +126,10 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
_domainHandler.parseDTLSRequirementPacket(packet);
break;
}
case PacketTypeIceServerHeartbeatResponse: {
_domainHandler.processICEResponsePacket(packet);
break;
}
case PacketTypePing: {
// send back a reply
SharedNodePointer matchingNode = sendingNodeForPacket(packet);
@ -158,6 +165,26 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
break;
}
case PacketTypeUnverifiedPing: {
// send back a reply
QByteArray replyPacket = constructPingReplyPacket(packet, _domainHandler.getICEClientID());
writeUnverifiedDatagram(replyPacket, senderSockAddr);
break;
}
case PacketTypeUnverifiedPingReply: {
qDebug() << "Received reply from domain-server on" << senderSockAddr;
// for now we're unsafely assuming this came back from the domain
if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) {
qDebug() << "Connecting to domain using local socket";
_domainHandler.activateICELocalSocket();
} else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) {
qDebug() << "Conecting to domain using public socket";
_domainHandler.activateICEPublicSocket();
} else {
qDebug() << "Reply does not match either local or public socket for domain. Will not connect.";
}
}
case PacketTypeStunResponse: {
// a STUN packet begins with 00, we've checked the second zero with packetVersionMatch
// pass it along so it can be processed into our public address and port
@ -242,8 +269,10 @@ void NodeList::sendDomainServerCheckIn() {
// we don't know our public socket and we need to send it to the domain server
// send a STUN request to figure it out
sendSTUNRequest();
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) {
handleICEConnectionToDomainServer();
} else if (!_domainHandler.getIP().isNull()) {
bool isUsingDTLS = false;
PacketType domainPacketType = !_domainHandler.isConnected()
@ -256,10 +285,17 @@ void NodeList::sendDomainServerCheckIn() {
// construct the DS check in packet
QUuid packetUUID = _sessionUUID;
if (!_domainHandler.getAssignmentUUID().isNull() && domainPacketType == PacketTypeDomainConnectRequest) {
// this is a connect request and we're an assigned node
// so set our packetUUID as the assignment UUID
packetUUID = _domainHandler.getAssignmentUUID();
if (domainPacketType == PacketTypeDomainConnectRequest) {
if (!_domainHandler.getAssignmentUUID().isNull()) {
// this is a connect request and we're an assigned node
// so set our packetUUID as the assignment UUID
packetUUID = _domainHandler.getAssignmentUUID();
} else if (_domainHandler.requiresICE()) {
// this is a connect request and we're an interface client
// that used ice to discover the DS
// so send our ICE client UUID with the connect request
packetUUID = _domainHandler.getICEClientID();
}
}
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
@ -267,8 +303,8 @@ void NodeList::sendDomainServerCheckIn() {
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
@ -298,6 +334,30 @@ void NodeList::sendDomainServerCheckIn() {
}
}
void NodeList::handleICEConnectionToDomainServer() {
if (_domainHandler.getICEPeer().isNull()
|| _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
_domainHandler.getICEPeer().resetConnectionAttemps();
LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(),
_domainHandler.getICEClientID(),
_domainHandler.getUUID());
} else {
qDebug() << "Sending ping packets to establish connectivity with domain-server with ID"
<< uuidStringWithoutCurlyBraces(_domainHandler.getUUID());
// send the ping packet to the local and public sockets for this nodfe
QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID());
writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket());
QByteArray publicPingPacket = constructPingPacket(PingType::Public, false, _domainHandler.getICEClientID());
writeUnverifiedDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket());
_domainHandler.getICEPeer().incrementConnectionAttempts();
}
}
int NodeList::processDomainServerList(const QByteArray& packet) {
// this is a packet from the domain server, reset the count of un-replied check-ins
_numNoReplyDomainCheckIns = 0;
@ -369,35 +429,6 @@ void NodeList::sendAssignment(Assignment& assignment) {
_nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort());
}
QByteArray NodeList::constructPingPacket(PingType_t pingType) {
QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing);
QDataStream packetStream(&pingPacket, QIODevice::Append);
packetStream << pingType;
packetStream << usecTimestampNow();
return pingPacket;
}
QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
QDataStream pingPacketStream(pingPacket);
pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket));
PingType_t typeFromOriginalPing;
pingPacketStream >> typeFromOriginalPing;
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();
return replyPacket;
}
void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
// send the ping packet to the local and public sockets for this node
@ -440,34 +471,3 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con
sendingNode->activateSymmetricSocket();
}
}
const QString QSETTINGS_GROUP_NAME = "NodeList";
const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname";
void NodeList::loadData(QSettings *settings) {
settings->beginGroup(DOMAIN_SERVER_SETTING_KEY);
QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString();
if (domainServerHostname.size() > 0) {
_domainHandler.setHostname(domainServerHostname);
} else {
_domainHandler.setHostname(DEFAULT_DOMAIN_HOSTNAME);
}
settings->endGroup();
}
void NodeList::saveData(QSettings* settings) {
settings->beginGroup(DOMAIN_SERVER_SETTING_KEY);
if (_domainHandler.getHostname() != DEFAULT_DOMAIN_HOSTNAME) {
// the user is using a different hostname, store it
settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHandler.getHostname()));
} else {
// the user has switched back to default, remove the current setting
settings->remove(DOMAIN_SERVER_SETTING_KEY);
}
settings->endGroup();
}

View file

@ -37,14 +37,6 @@ const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5;
class Assignment;
typedef quint8 PingType_t;
namespace PingType {
const PingType_t Agnostic = 0;
const PingType_t Local = 1;
const PingType_t Public = 2;
const PingType_t Symmetric = 3;
}
class NodeList : public LimitedNodeList {
Q_OBJECT
public:
@ -69,14 +61,8 @@ public:
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
void sendAssignment(Assignment& assignment);
QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic);
QByteArray constructPingReplyPacket(const QByteArray& pingPacket);
void pingPunchForInactiveNode(const SharedNodePointer& node);
void loadData(QSettings* settings);
void saveData(QSettings* settings);
public slots:
void reset();
void sendDomainServerCheckIn();
@ -91,6 +77,8 @@ private:
void sendSTUNRequest();
bool processSTUNResponse(const QByteArray& packet);
void handleICEConnectionToDomainServer();
void processDomainServerAuthRequest(const QByteArray& packet);
void requestAuthForDomainServer();
void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode);

View file

@ -16,7 +16,7 @@
OAuthAccessToken::OAuthAccessToken() :
token(),
refreshToken(),
expiryTimestamp(0),
expiryTimestamp(-1),
tokenType()
{

View file

@ -26,7 +26,7 @@ public:
QByteArray authorizationHeaderValue() const { return QString("Bearer %1").arg(token).toUtf8(); }
bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
bool isExpired() const { return expiryTimestamp != -1 && expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
QString token;
QString refreshToken;

View file

@ -70,7 +70,11 @@ enum PacketType {
PacketTypeVoxelEditNack,
PacketTypeParticleEditNack,
PacketTypeEntityEditNack, // 48
PacketTypeSignedTransactionPayment
PacketTypeSignedTransactionPayment,
PacketTypeIceServerHeartbeat,
PacketTypeIceServerHeartbeatResponse,
PacketTypeUnverifiedPing,
PacketTypeUnverifiedPingReply
};
typedef char PacketVersion;
@ -80,7 +84,9 @@ const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainOAuthRequest
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse
<< PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeEntityQuery
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack;
<< PacketTypeOctreeDataNack << PacketTypeVoxelEditNack << PacketTypeParticleEditNack << PacketTypeEntityEditNack
<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse
<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply;
const int NUM_BYTES_MD5_HASH = 16;
const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID;