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;