diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4c0bfb0892..62cdc925f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 24e9a5b63b..2fbe33a4e1 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -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.
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",
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 786fd58d61..61310cad75 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -64,7 +64,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators("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(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::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(node->getLinkedData());
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 57d62291bd..0ad2aae8a8 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -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 _webAuthenticationStateSet;
QHash _cookieSessionHash;
+ HifiSockAddr _localSockAddr;
+
+ QHash _connectingICEPeers;
+ QHash _connectedICEPeers;
+
DomainServerSettingsManager _settingsManager;
};
+
#endif // hifi_DomainServer_h
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index b5d7df65cf..28b1151f2d 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -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;
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index b60cb32dfd..c2e8a7d90d 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -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:
diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt
new file mode 100644
index 0000000000..c81ba16248
--- /dev/null
+++ b/ice-server/CMakeLists.txt
@@ -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()
\ No newline at end of file
diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp
new file mode 100644
index 0000000000..e3db679009
--- /dev/null
+++ b/ice-server/src/IceServer.cpp
@@ -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
+
+#include
+#include
+#include
+
+#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& 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& connections) {
+ QSet::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;
+ }
+ }
+}
diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h
new file mode 100644
index 0000000000..e15bda1211
--- /dev/null
+++ b/ice-server/src/IceServer.h
@@ -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
+#include
+#include
+
+#include
+
+typedef QHash NetworkPeerHash;
+
+class IceServer : public QCoreApplication {
+public:
+ IceServer(int argc, char* argv[]);
+private slots:
+ void processDatagrams();
+ void clearInactivePeers();
+private:
+
+ void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections);
+
+ QUuid _id;
+ QUdpSocket _serverSocket;
+ NetworkPeerHash _activePeers;
+ QHash > _currentConnections;
+};
+
+#endif // hifi_IceServer_h
\ No newline at end of file
diff --git a/ice-server/src/main.cpp b/ice-server/src/main.cpp
new file mode 100644
index 0000000000..21c8b563b1
--- /dev/null
+++ b/ice-server/src/main.cpp
@@ -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
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 485691e661..d41b50a232 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -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";
diff --git a/interface/src/Application.h b/interface/src/Application.h
index e50039a20e..ca26cffab8 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -140,6 +140,8 @@ class Application : public QApplication {
public:
static Application* getInstance() { return static_cast(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();
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 94af1ad80a..80f3b7770e 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -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();
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 2402090213..f73f4b02a2 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -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,
diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp
index 1e235b6116..f82fba98a0 100644
--- a/interface/src/MetavoxelSystem.cpp
+++ b/interface/src/MetavoxelSystem.cpp
@@ -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);
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index f7aa8a2bd6..aae1907b76 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -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);
diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp
index f5d838f95b..48ea85214a 100644
--- a/interface/src/devices/SixenseManager.cpp
+++ b/interface/src/devices/SixenseManager.cpp
@@ -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);
diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp
index bf529fb593..047b76dab6 100644
--- a/interface/src/scripting/LocationScriptingInterface.cpp
+++ b/interface/src/scripting/LocationScriptingInterface.cpp
@@ -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() {
diff --git a/jenkins/jobs.groovy b/jenkins/jobs.groovy
deleted file mode 100644
index 7cab63d42d..0000000000
--- a/jenkins/jobs.groovy
+++ /dev/null
@@ -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
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index f79b66b539..7d924d02de 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -59,7 +59,8 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
AccountManager::AccountManager() :
_authURL(),
_pendingCallbackMap(),
- _accountInfo()
+ _accountInfo(),
+ _shouldPersistToSettingsFile(true)
{
qRegisterMetaType("OAuthAccessToken");
qRegisterMetaTypeStreamOperators("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();
- 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();
+ 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";
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index 8d1a127408..3628b5a865 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -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 _pendingCallbackMap;
DataServerAccountInfo _accountInfo;
+ bool _shouldPersistToSettingsFile;
};
#endif // hifi_AccountManager_h
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index 4509a1790c..3d7617e17b 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -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;
}
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index f7cc7c52ee..fe6219b3f7 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -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
\ No newline at end of file
diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h
index dd9540718e..91675594ad 100644
--- a/libraries/networking/src/DataServerAccountInfo.h
+++ b/libraries/networking/src/DataServerAccountInfo.h
@@ -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; }
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index 9c0ae55d7e..fecbd1457f 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -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();
+ }
+}
diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h
index bfdb5d7f38..e9d7fb1725 100644
--- a/libraries/networking/src/DomainHandler.h
+++ b/libraries/networking/src/DomainHandler.h
@@ -20,6 +20,7 @@
#include
#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;
diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp
index 2c8e968375..507788009a 100644
--- a/libraries/networking/src/LimitedNodeList.cpp
+++ b/libraries/networking/src/LimitedNodeList.cpp
@@ -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);
+}
diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h
index b34845719c..a7ffc7ec28 100644
--- a/libraries/networking/src/LimitedNodeList.h
+++ b/libraries/networking/src/LimitedNodeList.h
@@ -52,6 +52,14 @@ typedef QSharedPointer SharedNodePointer;
typedef QHash 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();
diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp
new file mode 100644
index 0000000000..eaaf57471c
--- /dev/null
+++ b/libraries/networking/src/NetworkPeer.cpp
@@ -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
+
+#include
+#include
+
+#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;
+}
\ No newline at end of file
diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h
new file mode 100644
index 0000000000..bb92c54eb8
--- /dev/null
+++ b/libraries/networking/src/NetworkPeer.h
@@ -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
+#include
+
+#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 SharedNetworkPeer;
+
+#endif // hifi_NetworkPeer_h
\ No newline at end of file
diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp
index 662c827069..20461cb754 100644
--- a/libraries/networking/src/Node.cpp
+++ b/libraries/networking/src/Node.cpp
@@ -12,11 +12,12 @@
#include
#include
+#include
+
#include "Node.h"
#include "SharedUtil.h"
#include
-#include
#include
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;
diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h
index 0d5703b45c..994ddd3bec 100644
--- a/libraries/networking/src/Node.h
+++ b/libraries/networking/src/Node.h
@@ -21,6 +21,7 @@
#include
#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;
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 3a1ed79f77..50539e1196 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -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();
-}
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index 8293d0a05a..921f33b454 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -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);
diff --git a/libraries/networking/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp
index 365b07a935..b8ec58099f 100644
--- a/libraries/networking/src/OAuthAccessToken.cpp
+++ b/libraries/networking/src/OAuthAccessToken.cpp
@@ -16,7 +16,7 @@
OAuthAccessToken::OAuthAccessToken() :
token(),
refreshToken(),
- expiryTimestamp(0),
+ expiryTimestamp(-1),
tokenType()
{
diff --git a/libraries/networking/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h
index 167bb824da..fcd0f22e39 100644
--- a/libraries/networking/src/OAuthAccessToken.h
+++ b/libraries/networking/src/OAuthAccessToken.h
@@ -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;
diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h
index 207cf680d3..2d298d253d 100644
--- a/libraries/networking/src/PacketHeaders.h
+++ b/libraries/networking/src/PacketHeaders.h
@@ -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 NON_VERIFIED_PACKETS = QSet()
<< 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;