diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6bf5c62710..034b98e8db 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -65,9 +65,13 @@ DomainServer::DomainServer(int argc, char* argv[]) : _settingsManager(), _iceServerSocket(ICE_SERVER_DEFAULT_HOSTNAME, ICE_SERVER_DEFAULT_PORT) { + qInstallMessageHandler(LogHandler::verboseMessageHandler); + LogUtils::init(); Setting::init(); + connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); @@ -106,6 +110,11 @@ DomainServer::DomainServer(int argc, char* argv[]) : } } +void DomainServer::aboutToQuit() { + // clear the log handler so that Qt doesn't call the destructor on LogHandler + qInstallMessageHandler(0); +} + void DomainServer::restart() { qDebug() << "domain-server is restarting."; @@ -354,37 +363,23 @@ bool DomainServer::optionallySetupAssignmentPayment() { void DomainServer::setupAutomaticNetworking() { auto nodeList = DependencyManager::get(); - const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000; - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; - - // setup our timer to check our IP via stun every X seconds - QTimer* dynamicIPTimer = new QTimer(this); - connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - 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 sendHeartbeatToIceServer immediately anytime a local or public socket changes connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); - // attempt to update our public socket now, this will send a heartbeat once we get public socket - requestCurrentPublicSocketViaSTUN(); - - // in case the STUN lookup is still happening we should re-request a public socket once we get that address - connect(&nodeList->getSTUNSockAddr(), &HifiSockAddr::lookupCompleted, - this, &DomainServer::requestCurrentPublicSocketViaSTUN); + // we need this DS to know what our public IP is - start trying to figure that out now + nodeList->startSTUNPublicSocketUpdate(); + // setup a timer to heartbeat with the ice-server every so often + QTimer* iceHeartbeatTimer = new QTimer(this); + connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); + iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } if (!didSetupAccountManagerWithAccessToken()) { @@ -404,14 +399,12 @@ void DomainServer::setupAutomaticNetworking() { << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); if (_automaticNetworkingSetting == 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 + // send any public socket changes to the data server so nodes can find us at our new IP connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); - // attempt to update our sockets now - requestCurrentPublicSocketViaSTUN(); + // have the LNL enable public socket updating via STUN + nodeList->startSTUNPublicSocketUpdate(); } else { // send our heartbeat to data server so it knows what our network settings are sendHeartbeatToDataServer(); @@ -574,7 +567,6 @@ const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::EntityServer; void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { - NodeType_t nodeType; HifiSockAddr publicSockAddr, localSockAddr; @@ -640,9 +632,17 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock QUuid nodeUUID; - if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) { + HifiSockAddr discoveredSocket = senderSockAddr; + SharedNetworkPeer connectedPeer = _icePeers.value(packetUUID); + + if (connectedPeer) { // this user negotiated a connection with us via ICE, so re-use their ICE client ID nodeUUID = packetUUID; + + if (connectedPeer->getActiveSocket()) { + // set their discovered socket to whatever the activated socket on the network peer object was + discoveredSocket = *connectedPeer->getActiveSocket(); + } } else { // we got a packetUUID we didn't recognize, just add the node nodeUUID = QUuid::createUuid(); @@ -670,6 +670,10 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr, canAdjustLocks, canRez); + + // So that we can send messages to this node at will - we need to activate the correct socket on this node now + newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); + // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); @@ -699,10 +703,12 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock // reply back to the user with a PacketTypeDomainList sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet()); + + // send out this node to our other connected nodes + broadcastNewNode(newNode); } } - unsigned int DomainServer::countConnectedUsers() { unsigned int result = 0; auto nodeList = DependencyManager::get(); @@ -715,9 +721,9 @@ unsigned int DomainServer::countConnectedUsers() { } -bool DomainServer::verifyUsersKey (const QString& username, - const QByteArray& usernameSignature, - QString& reasonReturn) { +bool DomainServer::verifyUsersKey(const QString& username, + const QByteArray& usernameSignature, + QString& reasonReturn) { // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -912,26 +918,8 @@ int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType return packetStream.device()->pos(); } -NodeSet DomainServer::nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes) { - QDataStream packetStream(packet); - packetStream.skipRawData(numPreceedingBytes); - - quint8 numInterestTypes = 0; - packetStream >> numInterestTypes; - - quint8 nodeType; - NodeSet nodeInterestSet; - - for (int i = 0; i < numInterestTypes; i++) { - packetStream >> nodeType; - nodeInterestSet.insert((NodeType_t) nodeType); - } - - return nodeInterestSet; -} - void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr, - const NodeSet& nodeInterestList) { + const NodeSet& nodeInterestSet) { auto limitedNodeList = DependencyManager::get(); QByteArray broadcastPacket = limitedNodeList->byteArrayWithPopulatedHeader(PacketTypeDomainList); @@ -945,14 +933,10 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - // 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; - } + // store the nodeInterestSet on this DomainServerNodeData, in case it has changed + nodeData->setNodeInterestSet(nodeInterestSet); - if (nodeInterestList.size() > 0) { + if (nodeInterestSet.size() > 0) { // DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; int dataMTU = MAX_PACKET_SIZE; @@ -964,27 +948,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif QByteArray nodeByteArray; QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append); - if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) { + if (otherNode->getUUID() != node->getUUID() && nodeInterestSet.contains(otherNode->getType())) { // don't send avatar nodes to other avatars, that will come from avatar mixer nodeDataStream << *otherNode.data(); // pack the secret that these two nodes will use to communicate with each other - QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID()); - if (secretUUID.isNull()) { - // generate a new secret UUID these two nodes can use - secretUUID = QUuid::createUuid(); - - // set that on the current Node's sessionSecretHash - nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); - - // set it on the other Node's sessionSecretHash - reinterpret_cast(otherNode->getLinkedData()) - ->getSessionSecretHash().insert(node->getUUID(), secretUUID); - - } - - nodeDataStream << secretUUID; + nodeDataStream << connectionSecretForNodes(node, otherNode); if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { // we need to break here and start a new packet @@ -1005,7 +975,62 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif } // always write the last broadcastPacket - limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node, senderSockAddr); + limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node); +} + +QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { + DomainServerNodeData* nodeAData = dynamic_cast(nodeA->getLinkedData()); + DomainServerNodeData* nodeBData = dynamic_cast(nodeB->getLinkedData()); + + if (nodeAData && nodeBData) { + QUuid& secretUUID = nodeAData->getSessionSecretHash()[nodeB->getUUID()]; + + if (secretUUID.isNull()) { + // generate a new secret UUID these two nodes can use + secretUUID = QUuid::createUuid(); + + // set it on the other Node's sessionSecretHash + reinterpret_cast(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID); + } + + return secretUUID; + } + + return QUuid(); +} + +void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { + + auto limitedNodeList = DependencyManager::get(); + + // setup the add packet for this new node + QByteArray addNodePacket = limitedNodeList->byteArrayWithPopulatedHeader(PacketTypeDomainServerAddedNode); + QDataStream addNodeStream(&addNodePacket, QIODevice::Append); + + addNodeStream << *addedNode.data(); + + int connectionSecretIndex = addNodePacket.size(); + + limitedNodeList->eachMatchingNode( + [&](const SharedNodePointer& node)->bool { + if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) { + // is the added Node in this node's interest list? + DomainServerNodeData* nodeData = dynamic_cast(node->getLinkedData()); + return nodeData->getNodeInterestSet().contains(addedNode->getType()); + } else { + return false; + } + }, + [&](const SharedNodePointer& node) { + QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); + + // replace the bytes at the end of the packet for the connection secret between these nodes + addNodePacket.replace(connectionSecretIndex, NUM_BYTES_RFC4122_UUID, rfcConnectionSecret); + + // send off this packet to the node + limitedNodeList->writeUnverifiedDatagram(addNodePacket, node); + } + ); } void DomainServer::readAvailableDatagrams() { @@ -1216,10 +1241,6 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } } -void DomainServer::requestCurrentPublicSocketViaSTUN() { - DependencyManager::get()->sendSTUNRequest(); -} - QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address"; const QString SOCKET_PORT_KEY = "port"; @@ -1284,82 +1305,84 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } -// todo: have data-web respond with ice-server hostname to use - -void DomainServer::performICEUpdates() { - sendHeartbeatToIceServer(); - sendICEPingPackets(); -} +// TODO: have data-web respond with ice-server hostname to use void DomainServer::sendHeartbeatToIceServer() { DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); } -void DomainServer::sendICEPingPackets() { - auto nodeList = DependencyManager::get(); +const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; - QHash::iterator peer = _connectingICEPeers.begin(); +void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - while (peer != _connectingICEPeers.end()) { + if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) { + // we've reached the maximum number of ping attempts + qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); + qDebug() << "Removing from list of connecting peers."; - 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(); + _icePeers.remove(peer->getUUID()); + } else { + auto nodeList = DependencyManager::get(); - // 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()); + // 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()); + QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); + nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket()); - peer->incrementConnectionAttempts(); + peer->incrementConnectionAttempts(); + } +} - // go to next peer in hash - ++peer; +void DomainServer::handlePeerPingTimeout() { + NetworkPeer* senderPeer = qobject_cast(sender()); + + if (senderPeer) { + SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); + + if (sharedPeer && !sharedPeer->getActiveSocket()) { + pingPunchForConnectingPeer(sharedPeer); } } } -void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { +void DomainServer::processICEPeerInformation(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; + NetworkPeer* receivedPeer = new NetworkPeer; + iceResponseStream >> *receivedPeer; - while (!iceResponseStream.atEnd()) { - iceResponseStream >> receivedPeer; + if (!_icePeers.contains(receivedPeer->getUUID())) { + qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; + SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); + _icePeers[receivedPeer->getUUID()] = newPeer; - if (!_connectedICEPeers.contains(receivedPeer.getUUID())) { - if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { - qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; - } + // make sure we know when we should ping this peer + connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainServer::handlePeerPingTimeout); - _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; - } + // immediately ping the new peer, and start a timer to continue pinging it until we connect to it + newPeer->startPingTimer(); + + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << newPeer->getUUID(); + + pingPunchForConnectingPeer(newPeer); + } else { + delete receivedPeer; } } void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { QUuid nodeUUID = uuidFromPacketHeader(packet); - NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID); + SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); - if (!sendingPeer.isNull()) { + if (sendingPeer) { // 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()); - } + sendingPeer->activateMatchingOrNewSymmetricSocket(senderSockAddr); } } @@ -1427,8 +1450,8 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS processICEPingReply(receivedPacket, senderSockAddr); break; } - case PacketTypeIceServerHeartbeatResponse: - processICEHeartbeatResponse(receivedPacket); + case PacketTypeIceServerPeerInformation: + processICEPeerInformation(receivedPacket); break; default: break; @@ -2087,9 +2110,8 @@ 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()); + // if this peer connected via ICE then remove them from our ICE peers hash + _icePeers.remove(node->getUUID()); DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 9e9d3f13f3..f62ba89871 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -57,17 +57,17 @@ public slots: void restart(); private slots: + void aboutToQuit(); + void loginFailed(); void readAvailableDatagrams(); void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); - void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); - void performICEUpdates(); void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } void sendHeartbeatToIceServer(); - void sendICEPingPackets(); + void handlePeerPingTimeout(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -80,7 +80,9 @@ private: void setupAutomaticNetworking(); void sendHeartbeatToDataServer(const QString& networkAddress); void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); - void processICEHeartbeatResponse(const QByteArray& packet); + void processICEPeerInformation(const QByteArray& packet); + + void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); @@ -98,9 +100,11 @@ private: HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const HifiSockAddr& senderSockAddr); - NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes); void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, - const NodeSet& nodeInterestList); + const NodeSet& nodeInterestSet); + + QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); + void broadcastNewNode(const SharedNodePointer& node); void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); @@ -151,8 +155,7 @@ private: QHash _userPublicKeys; - QHash _connectingICEPeers; - QHash _connectedICEPeers; + QHash _icePeers; QString _automaticNetworkingSetting; diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 366ee8c730..a91a7e0b9c 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -12,44 +12,47 @@ #ifndef hifi_DomainServerNodeData_h #define hifi_DomainServerNodeData_h - #include #include #include #include #include +#include class DomainServerNodeData : public NodeData { public: DomainServerNodeData(); int parseData(const QByteArray& packet) { return 0; } - + const QJsonObject& getStatsJSONObject() const { return _statsJSONObject; } - + void parseJSONStatsPacket(const QByteArray& statsPacket); - + void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } - + void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } const QUuid& getWalletUUID() const { return _walletUUID; } - + void setUsername(const QString& username) { _username = username; } const QString& getUsername() const { return _username; } - + QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; } - + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } - + void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; } bool isAuthenticated() const { return _isAuthenticated; } - + QHash& getSessionSecretHash() { return _sessionSecretHash; } + + const NodeSet& getNodeInterestSet() const { return _nodeInterestSet; } + void setNodeInterestSet(const NodeSet& nodeInterestSet) { _nodeInterestSet = nodeInterestSet; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); - + QHash _sessionSecretHash; QUuid _assignmentUUID; QUuid _walletUUID; @@ -58,6 +61,7 @@ private: QJsonObject _statsJSONObject; HifiSockAddr _sendingSockAddr; bool _isAuthenticated; + NodeSet _nodeInterestSet; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index ba80e6fce0..790cc07c56 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -26,18 +26,16 @@ int main(int argc, char* argv[]) { #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - + int currentExitCode = 0; - + // use a do-while to handle domain-server restart do { DomainServer domainServer(argc, argv); currentExitCode = domainServer.exec(); } while (currentExitCode == DomainServer::EXIT_CODE_REBOOT); - - + + return currentExitCode; } diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index e72555cac1..8b5e5a4b39 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -33,133 +33,121 @@ IceServer::IceServer(int argc, char* argv[]) : qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_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) { + + PacketType packetType = packetTypeForPacket(incomingPacket); + + if (packetType == PacketTypeIceServerHeartbeat) { + SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(incomingPacket); + + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(sendingSockAddr); + } else if (packetType == PacketTypeIceServerQuery) { + + // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? 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); + + SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); + + if (matchingPeer) { + // we have the peer they want to connect to - send them pack the information for that peer + sendPeerInformationPacket(*(matchingPeer.data()), &sendingSockAddr); + + // we also need to send them to the active peer they are hoping to connect to + // create a dummy peer object we can pass to sendPeerInformationPacket + + NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); + sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); } } } } -void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections) { - QSet::iterator peerID = connections.begin(); - +SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket) { + 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); + } + + // update our last heard microstamp for this network peer to now + matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + + return matchingPeer; +} + +void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { QByteArray outgoingPacket(MAX_PACKET_SIZE, 0); - int currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); + int currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerPeerInformation, _id); int numHeaderBytes = currentPacketSize; - - // 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 = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); - } - - // append the current peer bytes - outgoingPacket.insert(currentPacketSize, peerBytes); - currentPacketSize += peerBytes.size(); - - ++peerID; - } - } - - if (currentPacketSize > numHeaderBytes) { - // write the last packet, if there is data in it - _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - } + + // get the byte array for this peer + QByteArray peerBytes = peer.toByteArray(); + outgoingPacket.replace(numHeaderBytes, peerBytes.size(), peerBytes); + + currentPacketSize += peerBytes.size(); + + // write the current packet + _serverSocket.writeDatagram(outgoingPacket.data(), outgoingPacket.size(), + 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); @@ -171,11 +159,9 @@ void IceServer::clearInactivePeers() { } bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { - // // We need an HTTP handler in order to monitor the health of the ice server // The correct functioning of the ICE server will be determined by its HTTP availability, - // - + if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (url.path() == "/status") { connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size())); diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index be6d298e3d..1f213fa606 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -31,14 +31,14 @@ private slots: void processDatagrams(); void clearInactivePeers(); private: - - void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections); - + + SharedNetworkPeer addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket); + void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr); + QUuid _id; QUdpSocket _serverSocket; NetworkPeerHash _activePeers; - QHash > _currentConnections; HTTPManager _httpManager; }; -#endif // hifi_IceServer_h \ No newline at end of file +#endif // hifi_IceServer_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ddd581fe53..6242318170 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -71,14 +71,14 @@ Menu::Menu() { { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); - + // connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item connect(&accountManager, &AccountManager::profileChanged, dialogsManager.data(), &DialogsManager::toggleLoginDialog); connect(&accountManager, &AccountManager::logoutComplete, dialogsManager.data(), &DialogsManager::toggleLoginDialog); } - + addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, qApp, SLOT(loadDialog())); @@ -92,7 +92,7 @@ Menu::Menu() { addDisabledActionAndSeparator(fileMenu, "Location"); qApp->getBookmarks()->setupMenus(this, fileMenu); - + addActionToQMenuAndActionHash(fileMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L, @@ -148,8 +148,8 @@ Menu::Menu() { SLOT(setEnabled(bool))); connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, + + addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, 0, // QML Qt::Key_Backslash, dialogsManager.data(), SLOT(showIRCLink())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, @@ -175,7 +175,7 @@ Menu::Menu() { discoverabilityManager.data(), SLOT(setVisibility())); visibilityGroup->addAction(visibleToNoOne); - connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, + connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, discoverabilityManager.data(), &DiscoverabilityManager::visibilityChanged); } @@ -196,7 +196,7 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); - + addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); @@ -245,17 +245,17 @@ Menu::Menu() { qApp, SLOT(setFullscreen(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, - 0, // QML Qt::Key_P, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, + 0, // QML Qt::Key_P, true, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, - 0, //QML Qt::SHIFT | Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, + 0, //QML Qt::SHIFT | Qt::Key_H, true); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, - 0, // QML Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, + 0, // QML Qt::Key_H, false, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, #ifdef Q_OS_MAC Qt::META | Qt::Key_H, #else @@ -285,8 +285,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, + addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, + Qt::CTRL | Qt::SHIFT | Qt::Key_L, qApp, SLOT(toggleLogDialog())); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); @@ -297,8 +297,8 @@ Menu::Menu() { MenuWrapper* developerMenu = addMenu("Developer"); MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, - 0, // QML Qt::SHIFT | Qt::Key_A, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, + 0, // QML Qt::SHIFT | Qt::Key_A, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); @@ -317,7 +317,7 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight7, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); - + MenuWrapper* shadowMenu = renderOptionsMenu->addMenu("Shadows"); QActionGroup* shadowGroup = new QActionGroup(shadowMenu); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); @@ -351,14 +351,14 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, // QML Qt::Key_Asterisk, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, DependencyManager::get().data(), SLOT(toggleGlowEffect(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Wireframe, Qt::ALT | Qt::Key_W, false); - addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, + addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, // QML Qt::SHIFT | Qt::Key_L, dialogsManager.data(), SLOT(lodTools())); @@ -384,7 +384,7 @@ Menu::Menu() { faceTrackerGroup->addAction(faceshiftFaceTracker); #endif #ifdef HAVE_DDE - QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, + QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, 0, true, qApp, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(ddeFaceTracker); @@ -404,13 +404,13 @@ Menu::Menu() { #endif #if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) faceTrackingMenu->addSeparator(); - addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, + addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, Qt::CTRL | Qt::SHIFT | Qt::Key_F, true, // DDE face tracking is on by default qApp, SLOT(toggleFaceTrackerMute())); addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, true); #endif - - auto avatarManager = DependencyManager::get(); + + auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); @@ -426,7 +426,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); - + MenuWrapper* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); #ifdef __APPLE__ addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, @@ -472,7 +472,11 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, dialogsManager.data(), SLOT(toggleDiskCacheEditor())); + addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, + dialogsManager.data(), SLOT(showDomainConnectionDialog())); + MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); + MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); @@ -510,7 +514,7 @@ Menu::Menu() { 0, audioIO.data(), SLOT(sendMuteEnvironmentPacket())); - + auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); @@ -548,7 +552,7 @@ Menu::Menu() { audioScopeFramesGroup->addAction(twentyFrames); audioScopeFramesGroup->addAction(fiftyFrames); } - + auto statsRenderer = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioStats, Qt::CTRL | Qt::SHIFT | Qt::Key_A, @@ -767,7 +771,7 @@ QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) { } else { menuActions = actions(); } - + foreach (QAction* menuAction, menuActions) { QString actionText = menuAction->text(); if (menuName == menuAction->text()) { @@ -868,14 +872,14 @@ MenuWrapper* Menu::addMenu(const QString& menuName) { } addTo = menu; } - + QMenuBar::repaint(); return menu; } void Menu::removeMenu(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { QString finalMenuPart; @@ -885,14 +889,14 @@ void Menu::removeMenu(const QString& menuName) { } else { QMenuBar::removeAction(action); } - + QMenuBar::repaint(); } } bool Menu::menuExists(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { return true; @@ -937,7 +941,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { if (!properties.shortcutKeySequence.isEmpty()) { shortcut = new QShortcut(properties.shortcutKeySequence, this); } - + // check for positioning requests int requestedPosition = properties.position; if (requestedPosition == UNSPECIFIED_POSITION && !properties.beforeItem.isEmpty()) { @@ -951,7 +955,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { requestedPosition = afterPosition + 1; } } - + QAction* menuItemAction = NULL; if (properties.isSeparator) { addDisabledActionAndSeparator(menuObj, properties.menuItemName, requestedPosition); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 12ec312a56..6107744abc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -58,15 +58,15 @@ class Menu : public QMenuBar { Q_OBJECT public: static Menu* getInstance(); - + void loadSettings(); void saveSettings(); - + MenuWrapper* getMenu(const QString& menuName); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); - + QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -80,9 +80,9 @@ public: const QKeySequence& shortcut = 0, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); - + void removeAction(MenuWrapper* menu, const QString& actionName); - + public slots: MenuWrapper* addMenu(const QString& menuName); void removeMenu(const QString& menuName); @@ -94,21 +94,21 @@ public slots: bool menuItemExists(const QString& menuName, const QString& menuitem); bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); - + private: static Menu* _instance; Menu(); - + typedef void(*settingsAction)(Settings&, QAction&); static void loadAction(Settings& settings, QAction& action); static void saveAction(Settings& settings, QAction& action); void scanMenuBar(settingsAction modifySetting); void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings); - + /// helper method to have separators with labels that are also compatible with OS X void addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); - + QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -116,15 +116,15 @@ private: const QObject* receiver = NULL, const char* member = NULL, int menuItemLocation = UNSPECIFIED_POSITION); - + QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); - + QAction* getMenuAction(const QString& menuName); int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem); int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition); - + QHash _actionHash; }; @@ -262,6 +262,7 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; const QString SimpleShadows = "Simple"; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 56dd69eeeb..1170e3c3a6 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -24,6 +24,7 @@ #include "BandwidthDialog.h" #include "CachesSizeDialog.h" #include "DiskCacheEditor.h" +#include "DomainConnectionDialog.h" #include "HMDToolsDialog.h" #include "LodToolsDialog.h" #include "LoginDialog.h" @@ -52,7 +53,7 @@ void DialogsManager::showLoginDialog() { void DialogsManager::octreeStatsDetails() { if (!_octreeStatsDialog) { _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); - + if (_hmdToolsDialog) { _hmdToolsDialog->watchWindow(_octreeStatsDialog->windowHandle()); } @@ -65,7 +66,7 @@ void DialogsManager::octreeStatsDetails() { void DialogsManager::cachesSizeDialog() { if (!_cachesSizeDialog) { maybeCreateDialog(_cachesSizeDialog); - + connect(_cachesSizeDialog, SIGNAL(closed()), _cachesSizeDialog, SLOT(deleteLater())); _cachesSizeDialog->show(); } @@ -112,11 +113,11 @@ void DialogsManager::bandwidthDetails() { if (! _bandwidthDialog) { _bandwidthDialog = new BandwidthDialog(qApp->getWindow()); connect(_bandwidthDialog, SIGNAL(closed()), _bandwidthDialog, SLOT(deleteLater())); - + if (_hmdToolsDialog) { _hmdToolsDialog->watchWindow(_bandwidthDialog->windowHandle()); } - + _bandwidthDialog->show(); } _bandwidthDialog->raise(); @@ -125,7 +126,7 @@ void DialogsManager::bandwidthDetails() { void DialogsManager::lodTools() { if (!_lodToolsDialog) { maybeCreateDialog(_lodToolsDialog); - + connect(_lodToolsDialog, SIGNAL(closed()), _lodToolsDialog, SLOT(deleteLater())); _lodToolsDialog->show(); } @@ -172,7 +173,20 @@ void DialogsManager::showIRCLink() { _ircInfoBox->setAttribute(Qt::WA_DeleteOnClose); _ircInfoBox->show(); } - + _ircInfoBox->raise(); } +void DialogsManager::showDomainConnectionDialog() { + // if the dialog already exists we delete it so the connection data is refreshed + if (_domainConnectionDialog) { + _domainConnectionDialog->close(); + _domainConnectionDialog->deleteLater(); + _domainConnectionDialog = NULL; + } + + maybeCreateDialog(_domainConnectionDialog); + + _domainConnectionDialog->show(); + _domainConnectionDialog->raise(); +} diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index d5d9c33a9a..fc2dad072b 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -34,18 +34,19 @@ class PreferencesDialog; class ScriptEditorWindow; class QMessageBox; class AvatarAppearanceDialog; +class DomainConnectionDialog; class DialogsManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - + public: QPointer getBandwidthDialog() const { return _bandwidthDialog; } QPointer getHMDToolsDialog() const { return _hmdToolsDialog; } QPointer getLodToolsDialog() const { return _lodToolsDialog; } QPointer getOctreeStatsDialog() const { return _octreeStatsDialog; } QPointer getPreferencesDialog() const { return _preferencesDialog; } - + public slots: void toggleAddressBar(); void toggleDiskCacheEditor(); @@ -62,14 +63,15 @@ public slots: void showScriptEditor(); void showIRCLink(); void changeAvatarAppearance(); + void showDomainConnectionDialog(); private slots: void toggleToolWindow(); void hmdToolsClosed(); - + private: DialogsManager() {} - + template void maybeCreateDialog(QPointer& member) { if (!member) { @@ -77,13 +79,13 @@ private: Q_CHECK_PTR(parent); member = new T(parent); Q_CHECK_PTR(member); - + if (_hmdToolsDialog && member->windowHandle()) { _hmdToolsDialog->watchWindow(member->windowHandle()); } } } - + QPointer _addressBarDialog; QPointer _animationsDialog; QPointer _attachmentsDialog; @@ -98,6 +100,7 @@ private: QPointer _preferencesDialog; QPointer _scriptEditor; QPointer _avatarAppearanceDialog; + QPointer _domainConnectionDialog; }; -#endif // hifi_DialogsManager_h \ No newline at end of file +#endif // hifi_DialogsManager_h diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp new file mode 100644 index 0000000000..c0471dc5e1 --- /dev/null +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -0,0 +1,96 @@ +// +// DomainConnectionDialog.cpp +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 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 +#include + +#include "DomainConnectionDialog.h" + +DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint) +{ + setWindowTitle("Domain Connection Timing"); + setAttribute(Qt::WA_DeleteOnClose); + + // setup a QTableWidget so we can populate it with our values + QTableWidget* timeTable = new QTableWidget; + + timeTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + const QStringList TABLE_HEADERS = QStringList() << "Name" << "Timestamp (ms)" << "Delta (ms)" << "Time elapsed (ms)"; + + timeTable->setColumnCount(TABLE_HEADERS.size()); + + // ask the NodeList for the current values for connection times + QMap times = DependencyManager::get()->getLastConnectionTimes(); + + timeTable->setRowCount(times.size()); + + timeTable->setHorizontalHeaderLabels(TABLE_HEADERS); + + // setup our data with the values from the NodeList + quint64 firstStepTime = times.firstKey() / USECS_PER_MSEC; + quint64 lastStepTime = firstStepTime; + + int thisRow = 0; + + const QMetaObject &nodeListMeta = NodeList::staticMetaObject; + QMetaEnum stepEnum = nodeListMeta.enumerator(nodeListMeta.indexOfEnumerator("ConnectionStep")); + + foreach(quint64 timestamp, times.keys()) { + // When did this step occur, how long since the last step, how long since the start? + quint64 stepTime = timestamp / USECS_PER_MSEC; + quint64 delta = (stepTime - lastStepTime); + quint64 elapsed = stepTime - firstStepTime; + + lastStepTime = stepTime; + + // setup the columns for this row in the table + int stepIndex = (int) times.value(timestamp); + + timeTable->setItem(thisRow, 0, new QTableWidgetItem(stepEnum.valueToKey(stepIndex))); + timeTable->setItem(thisRow, 1, new QTableWidgetItem(QString::number(stepTime))); + timeTable->setItem(thisRow, 2, new QTableWidgetItem(QString::number(delta))); + timeTable->setItem(thisRow, 3, new QTableWidgetItem(QString::number(elapsed))); + + ++thisRow; + } + + // setup a horizontal box layout + QHBoxLayout* hBoxLayout = new QHBoxLayout; + hBoxLayout->addWidget(timeTable); + hBoxLayout->setSizeConstraint(QLayout::SetMinimumSize); + + // resize the table columns + timeTable->resizeColumnsToContents(); + + // figure out the size of the table so we can size the dialog + int tableWidth = timeTable->verticalHeader()->sizeHint().width(); + for (int i = 0; i < timeTable->columnCount(); i++) { + tableWidth += timeTable->columnWidth(i); + } + + int tableHeight = timeTable->horizontalHeader()->sizeHint().height(); + for (int i = 0; i < timeTable->rowCount(); i++) { + tableHeight += timeTable->rowHeight(i); + } + + // set the minimum size of the table to whatever we got + timeTable->setMinimumSize(tableWidth, tableHeight); + + setLayout(hBoxLayout); + adjustSize(); +} diff --git a/interface/src/ui/DomainConnectionDialog.h b/interface/src/ui/DomainConnectionDialog.h new file mode 100644 index 0000000000..0f49a1ee68 --- /dev/null +++ b/interface/src/ui/DomainConnectionDialog.h @@ -0,0 +1,25 @@ +// +// DomainConnectionDialog.h +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 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_DomainConnectionDialog_h +#define hifi_DomainConnectionDialog_h + +#pragma once + +#include + +class DomainConnectionDialog : public QDialog { + Q_OBJECT +public: + DomainConnectionDialog(QWidget* parent); +}; + +#endif // hifi_DomainConnectionDialog_h diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 10e2bc3bbf..41c8cb9537 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -116,7 +116,7 @@ AudioClient::AudioClient() : { // clear the array of locally injected samples memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL); - + connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &AudioClient::processReceivedSamples, Qt::DirectConnection); @@ -127,7 +127,7 @@ AudioClient::AudioClient() : QTimer* updateTimer = new QTimer(this); connect(updateTimer, &QTimer::timeout, this, &AudioClient::checkDevices); updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); - + // create GVerb filter _gverb = createGverbFilter(); configureGverbFilter(_gverb); @@ -135,7 +135,7 @@ AudioClient::AudioClient() : AudioClient::~AudioClient() { stop(); - + if (_gverb) { gverb_free(_gverb); } @@ -148,7 +148,7 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); - + gverb_flush(_gverb); } @@ -186,7 +186,7 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio int numSourceSamples) { float ratio = (float) destinationFormat.channelCount() / sourceFormat.channelCount(); ratio *= (float) destinationFormat.sampleRate() / sourceFormat.sampleRate(); - + return (numSourceSamples * ratio) + 0.5f; } @@ -287,7 +287,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } qCDebug(audioclient) << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; - + return getNamedAudioDeviceForMode(mode, deviceName); #endif @@ -302,7 +302,7 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, if (!audioDevice.isFormatSupported(desiredAudioFormat)) { qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; qCDebug(audioclient, "The desired audio format is not supported by this device"); - + if (desiredAudioFormat.channelCount() == 1) { adjustedAudioFormat = desiredAudioFormat; adjustedAudioFormat.setChannelCount(2); @@ -313,17 +313,17 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, adjustedAudioFormat.setChannelCount(1); } } - + const int FORTY_FOUR = 44100; - + adjustedAudioFormat = desiredAudioFormat; - + #ifdef Q_OS_ANDROID adjustedAudioFormat.setSampleRate(FORTY_FOUR); #else - + const int HALF_FORTY_FOUR = FORTY_FOUR / 2; - + if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { // use 48, which is a sample downsample, upsample adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); @@ -335,7 +335,7 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, adjustedAudioFormat.setSampleRate(FORTY_FOUR); } #endif - + if (adjustedAudioFormat != desiredAudioFormat) { // return the nearest in case it needs 2 channels adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); @@ -357,18 +357,18 @@ bool sampleChannelConversion(const int16_t* sourceSamples, int16_t* destinationS for (uint i = 0; i < numSourceSamples; i += 2) { destinationSamples[i / 2] = (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 2); } - + return true; } else if (sourceAudioFormat.channelCount() == 1 && destinationAudioFormat.channelCount() == 2) { - + // loop through the mono input audio and repeat each sample twice for (uint i = 0; i < numSourceSamples; ++i) { destinationSamples[i * 2] = destinationSamples[(i * 2) + 1] = sourceSamples[i]; } - + return true; } - + return false; } @@ -376,7 +376,7 @@ soxr_error_t possibleResampling(soxr_t resampler, const int16_t* sourceSamples, int16_t* destinationSamples, unsigned int numSourceSamples, unsigned int numDestinationSamples, const QAudioFormat& sourceAudioFormat, const QAudioFormat& destinationAudioFormat) { - + if (numSourceSamples > 0) { if (!resampler) { if (!sampleChannelConversion(sourceSamples, destinationSamples, numSourceSamples, @@ -384,41 +384,41 @@ soxr_error_t possibleResampling(soxr_t resampler, // no conversion, we can copy the samples directly across memcpy(destinationSamples, sourceSamples, numSourceSamples * sizeof(int16_t)); } - + return 0; } else { soxr_error_t resampleError = 0; - + if (sourceAudioFormat.channelCount() != destinationAudioFormat.channelCount()) { float channelCountRatio = (float) destinationAudioFormat.channelCount() / sourceAudioFormat.channelCount(); - + int numChannelCoversionSamples = (int) (numSourceSamples * channelCountRatio); int16_t* channelConversionSamples = new int16_t[numChannelCoversionSamples]; - + sampleChannelConversion(sourceSamples, channelConversionSamples, numSourceSamples, sourceAudioFormat, destinationAudioFormat); - + resampleError = soxr_process(resampler, channelConversionSamples, numChannelCoversionSamples, NULL, destinationSamples, numDestinationSamples, NULL); - + delete[] channelConversionSamples; } else { - + unsigned int numAdjustedSourceSamples = numSourceSamples; unsigned int numAdjustedDestinationSamples = numDestinationSamples; - + if (sourceAudioFormat.channelCount() == 2 && destinationAudioFormat.channelCount() == 2) { numAdjustedSourceSamples /= 2; numAdjustedDestinationSamples /= 2; } - + resampleError = soxr_process(resampler, sourceSamples, numAdjustedSourceSamples, NULL, destinationSamples, numAdjustedDestinationSamples, NULL); } - + return resampleError; } } else { @@ -429,30 +429,30 @@ soxr_error_t possibleResampling(soxr_t resampler, soxr_t soxrResamplerFromInputFormatToOutputFormat(const QAudioFormat& sourceAudioFormat, const QAudioFormat& destinationAudioFormat) { soxr_error_t soxrError; - + // setup soxr_io_spec_t for input and output soxr_io_spec_t inputToNetworkSpec = soxr_io_spec(soxrDataTypeFromQAudioFormat(sourceAudioFormat), soxrDataTypeFromQAudioFormat(destinationAudioFormat)); - + // setup soxr_quality_spec_t for quality options soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); - + int channelCount = (sourceAudioFormat.channelCount() == 2 && destinationAudioFormat.channelCount() == 2) ? 2 : 1; - + soxr_t newResampler = soxr_create(sourceAudioFormat.sampleRate(), destinationAudioFormat.sampleRate(), channelCount, &soxrError, &inputToNetworkSpec, &qualitySpec, 0); - + if (soxrError) { qCDebug(audioclient) << "There was an error setting up the soxr resampler -" << "soxr error code was " << soxrError; - + soxr_delete(newResampler); - + return NULL; } - + return newResampler; } @@ -476,7 +476,7 @@ void AudioClient::start() { QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName(); bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); - + if (!inputFormatSupported) { qCDebug(audioclient) << "Unable to set up audio input because of a problem with input format."; qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.nearestFormat(_desiredInputFormat); @@ -503,11 +503,11 @@ void AudioClient::stop() { _sourceGain.finalize(); _noiseSource.finalize(); _toneSource.finalize(); - + // "switch" to invalid devices in order to shut down the state switchInputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo()); - + if (_loopbackResampler) { soxr_delete(_loopbackResampler); _loopbackResampler = NULL; @@ -543,7 +543,7 @@ ty_gverb* AudioClient::createGverbFilter() { _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), _reverbOptions->getTailLevel()); - + return filter; } @@ -560,7 +560,7 @@ void AudioClient::configureGverbFilter(ty_gverb* filter) { void AudioClient::updateGverbOptions() { bool reverbChanged = false; if (_receivedAudioStream.hasReverb()) { - + if (_zoneReverbOptions.getReverbTime() != _receivedAudioStream.getRevebTime()) { _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); reverbChanged = true; @@ -569,7 +569,7 @@ void AudioClient::updateGverbOptions() { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); // Not part of actual filter config, no need to set reverbChanged to true } - + if (_reverbOptions != &_zoneReverbOptions) { _reverbOptions = &_zoneReverbOptions; reverbChanged = true; @@ -578,7 +578,7 @@ void AudioClient::updateGverbOptions() { _reverbOptions = &_scriptReverbOptions; reverbChanged = true; } - + if (reverbChanged) { gverb_free(_gverb); _gverb = createGverbFilter(); @@ -588,7 +588,7 @@ void AudioClient::updateGverbOptions() { void AudioClient::setReverb(bool reverb) { _reverb = reverb; - + if (!_reverb) { gverb_flush(_gverb); } @@ -620,7 +620,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve QAudioFormat& audioFormat, bool noEcho) { float wetFraction = DB_CO(_reverbOptions->getWetLevel()); float dryFraction = 1.0f - wetFraction; - + float lValue,rValue; for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { // Run GVerb @@ -634,7 +634,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)lResult; - + if (noEcho) { reverbAlone[j] = (int16_t)lValue * wetFraction; } @@ -643,7 +643,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)rResult; - + if (noEcho) { reverbAlone[j] = (int16_t)rValue * wetFraction; } @@ -660,53 +660,53 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) { return; } - + // if this person wants local loopback add that to the locally injected audio // if there is reverb apply it to local audio and substract the origin samples - + if (!_loopbackOutputDevice && _loopbackAudioOutput) { // we didn't have the loopback output device going so set that up now _loopbackOutputDevice = _loopbackAudioOutput->start(); - + if (!_loopbackOutputDevice) { return; } } - + // do we need to setup a resampler? if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) { qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback."; _loopbackResampler = soxrResamplerFromInputFormatToOutputFormat(_inputFormat, _outputFormat); - + if (!_loopbackResampler) { return; } } - + static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; - + int numInputSamples = inputByteArray.size() / sizeof(int16_t); int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); - + reverbAlone.resize(numInputSamples * sizeof(int16_t)); loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); - + int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - + if (hasReverb) { updateGverbOptions(); addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, _inputFormat, !_shouldEchoLocally); } - + possibleResampling(_loopbackResampler, (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); - + _loopbackOutputDevice->write(loopBackByteArray); } @@ -726,7 +726,7 @@ void AudioClient::handleAudioInput() { int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio); QByteArray inputByteArray = _inputDevice->readAll(); - + // Add audio source injection if enabled if (!_muted && _audioSourceInjectEnabled) { int16_t* inputFrameData = (int16_t*)inputByteArray.data(); @@ -745,11 +745,11 @@ void AudioClient::handleAudioInput() { _sourceGain.render(_inputFrameBuffer); // post gain _inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/); } - + handleLocalEchoAndReverb(inputByteArray); _inputRingBuffer.writeData(inputByteArray.data(), inputByteArray.size()); - + float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC)); _stats.updateInputMsecsRead(audioInputMsecsRead); @@ -763,50 +763,50 @@ void AudioClient::handleAudioInput() { : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; if (!_muted) { - + // zero out the monoAudioSamples array and the locally injected audio memset(networkAudioSamples, 0, numNetworkBytes); - + // Increment the time since the last clip if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += (float) numNetworkSamples / (float) AudioConstants::SAMPLE_RATE; } - + int16_t* inputAudioSamples = new int16_t[inputSamplesRequired]; _inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired); - + possibleResampling(_inputToNetworkResampler, inputAudioSamples, networkAudioSamples, inputSamplesRequired, numNetworkSamples, _inputFormat, _desiredInputFormat); - + delete[] inputAudioSamples; - + // only impose the noise gate and perform tone injection if we are sending mono audio if (!_isStereoInput && !_audioSourceInjectEnabled && _isNoiseGateEnabled) { _inputGate.gateSamples(networkAudioSamples, numNetworkSamples); - + // if we performed the noise gate we can get values from it instead of enumerating the samples again _lastInputLoudness = _inputGate.getLastLoudness(); - + if (_inputGate.clippedInLastFrame()) { _timeSinceLastClip = 0.0f; } } else { float loudness = 0.0f; - + for (int i = 0; i < numNetworkSamples; i++) { int thisSample = std::abs(networkAudioSamples[i]); loudness += (float) thisSample; - + if (thisSample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)) { _timeSinceLastClip = 0.0f; } } - + _lastInputLoudness = fabs(loudness / numNetworkSamples); } - + emit inputReceived(QByteArray(reinterpret_cast(networkAudioSamples), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(AudioConstants::AudioSample))); @@ -814,13 +814,13 @@ void AudioClient::handleAudioInput() { // our input loudness is 0, since we're muted _lastInputLoudness = 0; _timeSinceLastClip = 0.0f; - + _inputRingBuffer.shiftReadPosition(inputSamplesRequired); } auto nodeList = DependencyManager::get(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer && audioMixer->getActiveSocket()) { glm::vec3 headPosition = _positionGetter(); glm::quat headOrientation = _orientationGetter(); @@ -852,7 +852,7 @@ void AudioClient::handleAudioInput() { // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); currentPacketPtr += (sizeof(headPosition)); - + // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); @@ -864,7 +864,7 @@ void AudioClient::handleAudioInput() { // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); currentPacketPtr += (sizeof(headPosition)); - + // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); @@ -875,6 +875,8 @@ void AudioClient::handleAudioInput() { _stats.sentPacket(); + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); + int packetBytes = currentPacketPtr - audioDataPacket; nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -890,7 +892,7 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); const int16_t* receivedSamples = reinterpret_cast(inputBuffer.data()); - + // copy the packet from the RB to the output possibleResampling(_networkToOutputResampler, receivedSamples, reinterpret_cast(outputBuffer.data()), @@ -900,20 +902,20 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr void AudioClient::sendMuteEnvironmentPacket() { auto nodeList = DependencyManager::get(); - + QByteArray mutePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeMuteEnvironment); int headerSize = mutePacket.size(); - + const float MUTE_RADIUS = 50; - + glm::vec3 currentSourcePosition = _positionGetter(); mutePacket.resize(mutePacket.size() + sizeof(glm::vec3) + sizeof(float)); memcpy(mutePacket.data() + headerSize, ¤tSourcePosition, sizeof(glm::vec3)); memcpy(mutePacket.data() + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float)); - + // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer) { // send off this mute packet nodeList->writeDatagram(mutePacket, audioMixer); @@ -921,6 +923,8 @@ void AudioClient::sendMuteEnvironmentPacket() { } void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveFirstAudioPacket); + if (_audioOutput) { // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(audioByteArray); @@ -930,11 +934,11 @@ void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { void AudioClient::parseAudioEnvironmentData(const QByteArray &packet) { int numBytesPacketHeader = numBytesForPacketHeader(packet); const char* dataAt = packet.constData() + numBytesPacketHeader; - + char bitset; memcpy(&bitset, dataAt, sizeof(char)); dataAt += sizeof(char); - + bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT);; if (hasReverb) { float reverbTime, wetLevel; @@ -956,13 +960,13 @@ void AudioClient::toggleMute() { void AudioClient::setIsStereoInput(bool isStereoInput) { if (isStereoInput != _isStereoInput) { _isStereoInput = isStereoInput; - + if (_isStereoInput) { _desiredInputFormat.setChannelCount(2); } else { _desiredInputFormat.setChannelCount(1); } - + // change in channel count for desired input format, restart the input device switchInputToAudioDevice(_inputAudioDeviceName); } @@ -976,7 +980,7 @@ void AudioClient::selectAudioSourcePinkNoise() { _noiseSourceEnabled = true; _toneSourceEnabled = false; } - + void AudioClient::selectAudioSourceSine440() { _toneSourceEnabled = true; _noiseSourceEnabled = false; @@ -986,24 +990,24 @@ bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) { if (injector->getLocalBuffer()) { QAudioFormat localFormat = _desiredOutputFormat; localFormat.setChannelCount(isStereo ? 2 : 1); - + QAudioOutput* localOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName), localFormat, injector->getLocalBuffer()); - + // move the localOutput to the same thread as the local injector buffer localOutput->moveToThread(injector->getLocalBuffer()->thread()); - + // have it be stopped when that local buffer is about to close connect(injector->getLocalBuffer(), &AudioInjectorLocalBuffer::bufferEmpty, localOutput, &QAudioOutput::stop); connect(injector->getLocalBuffer(), &QIODevice::aboutToClose, localOutput, &QAudioOutput::stop); - + qCDebug(audioclient) << "Starting QAudioOutput for local injector" << localOutput; - + localOutput->start(injector->getLocalBuffer()); return localOutput->state() == QAudio::ActiveState; } - + return false; } @@ -1015,7 +1019,7 @@ void AudioClient::outputFormatChanged() { bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { bool supportedFormat = false; - + // cleanup any previously initialized device if (_audioInput) { // The call to stop() causes _inputDevice to be destructed. @@ -1030,7 +1034,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn _inputAudioDeviceName = ""; } - + if (_inputToNetworkResampler) { // if we were using an input to network resampler, delete it here soxr_delete(_inputToNetworkResampler); @@ -1040,36 +1044,36 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn if (!inputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; _inputAudioDeviceName = inputDeviceInfo.deviceName().trimmed(); - + if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; - + // we've got the best we can get for input // if required, setup a soxr resampler for this input to our desired network format if (_inputFormat != _desiredInputFormat && _inputFormat.sampleRate() != _desiredInputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a soxr resampler for input format to network format."; _inputToNetworkResampler = soxrResamplerFromInputFormatToOutputFormat(_inputFormat, _desiredInputFormat); - + if (!_inputToNetworkResampler) { return false; } } else { qCDebug(audioclient) << "No resampling required for audio input to match desired network format."; } - + // if the user wants stereo but this device can't provide then bail - if (!_isStereoInput || _inputFormat.channelCount() == 2) { + if (!_isStereoInput || _inputFormat.channelCount() == 2) { _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); _numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat); _audioInput->setBufferSize(_numInputCallbackBytes); - + // how do we want to handle input working, but output not working? int numFrameSamples = calculateNumberOfFrameSamples(_numInputCallbackBytes); _inputRingBuffer.resizeForFrameSize(numFrameSamples); - + _inputDevice = _audioInput->start(); - + if (_inputDevice) { connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput())); supportedFormat = true; @@ -1079,7 +1083,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn } } } - + return supportedFormat; } @@ -1117,7 +1121,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice // cleanup any previously initialized device if (_audioOutput) { _audioOutput->stop(); - + delete _audioOutput; _audioOutput = NULL; @@ -1125,13 +1129,13 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice delete _loopbackAudioOutput; _loopbackAudioOutput = NULL; } - + if (_networkToOutputResampler) { // if we were using an input to network resampler, delete it here soxr_delete(_networkToOutputResampler); _networkToOutputResampler = NULL; } - + if (_loopbackResampler) { // if we were using an input to output resample, delete it here soxr_delete(_loopbackResampler); @@ -1141,24 +1145,24 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); - + if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { qCDebug(audioclient) << "The format to be used for audio output is" << _outputFormat; - + // we've got the best we can get for input // if required, setup a soxr resampler for this input to our desired network format if (_desiredOutputFormat != _outputFormat && _desiredOutputFormat.sampleRate() != _outputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a resampler for network format to output format."; _networkToOutputResampler = soxrResamplerFromInputFormatToOutputFormat(_desiredOutputFormat, _outputFormat); - + if (!_networkToOutputResampler) { return false; } } else { qCDebug(audioclient) << "No resampling required for network output to match actual output format."; } - + outputFormatChanged(); // setup our general output device for audio-mixer audio @@ -1168,20 +1172,20 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice connect(_audioOutput, &QAudioOutput::notify, this, &AudioClient::outputNotify); qCDebug(audioclient) << "Output Buffer capacity in frames: " << _audioOutput->bufferSize() / sizeof(int16_t) / (float)_outputFrameSize; - + _audioOutputIODevice.start(); _audioOutput->start(&_audioOutputIODevice); // setup a loopback audio output device _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - + _timeSinceLastReceived.start(); supportedFormat = true; } } - + return supportedFormat; } @@ -1202,7 +1206,7 @@ void AudioClient::setOutputBufferSize(int numFrames) { // The following constant is operating system dependent due to differences in // the way input audio is handled. The audio input buffer size is inversely -// proportional to the accelerator ratio. +// proportional to the accelerator ratio. #ifdef Q_OS_WIN const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.1f; @@ -1226,7 +1230,7 @@ int AudioClient::calculateNumberOfInputCallbackBytes(const QAudioFormat& format) } float AudioClient::calculateDeviceToNetworkInputRatio() const { - float inputToNetworkInputRatio = (int)((_numInputCallbackBytes + float inputToNetworkInputRatio = (int)((_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL) + 0.5f); @@ -1257,7 +1261,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { int samplesRequested = maxSize / sizeof(int16_t); int samplesPopped; int bytesWritten; - + if ((samplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) { AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples((int16_t*)data, samplesPopped); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 48d9655e43..3c6b7bd3f5 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -106,6 +106,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::LookupAddress); + // there are 4 possible lookup strings // 1. global place name (name of domain or place) - example: sanfrancisco @@ -202,6 +204,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString DOMAIN_NETWORK_PORT_KEY = "network_port"; const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); + if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); @@ -432,6 +436,8 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); + emit possibleDomainChangeRequired(hostname, port); } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index f7d460d7dd..38d1ade2ad 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -31,18 +31,19 @@ DomainHandler::DomainHandler(QObject* parent) : _iceDomainID(), _iceClientID(), _iceServerSockAddr(), - _icePeer(), + _icePeer(this), _isConnected(false), _settingsObject(), _failedSettingsRequests(0) { - + // if we get a socket that make sure our NetworkPeer ping timer stops + connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); } void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); - _icePeer = NetworkPeer(); + _icePeer.reset(); if (requiresICE()) { // if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server @@ -84,6 +85,10 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos _sockAddr = sockAddr; } + if (!_sockAddr.isNull()) { + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); + } + // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification _hostname = hostname; } @@ -111,6 +116,8 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname); + UserActivityLogger::getInstance().changedDomain(_hostname); emit hostnameChanged(_hostname); } @@ -135,6 +142,18 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + auto nodeList = DependencyManager::get(); + + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerHostname); + + if (_iceServerSockAddr.getAddress().isNull()) { + // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up + connect(&_iceServerSockAddr, &HifiSockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup); + } else { + completedIceServerHostnameLookup(); + } + + // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); @@ -143,12 +162,14 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, } void DomainHandler::activateICELocalSocket() { + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getLocalSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); } void DomainHandler::activateICEPublicSocket() { + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getPublicSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); @@ -159,6 +180,8 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { _sockAddr.setAddress(hostInfo.addresses()[i]); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); + qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); @@ -172,6 +195,15 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { qCDebug(networking, "Failed domain server lookup"); } +void DomainHandler::completedIceServerHostnameLookup() { + qDebug() << "ICE server socket is at" << _iceServerSockAddr; + + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket); + + // emit our signal so we can send a heartbeat to ice-server immediately + emit iceSocketAndIDReceived(); +} + void DomainHandler::setIsConnected(bool isConnected) { if (_isConnected != isConnected) { _isConnected = isConnected; @@ -267,15 +299,20 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { QDataStream iceResponseStream(icePacket); iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket)); - NetworkPeer packetPeer; - iceResponseStream >> packetPeer; + iceResponseStream >> _icePeer; - if (packetPeer.getUUID() != _iceDomainID) { + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); + + if (_icePeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; + _icePeer.reset(); } else { - qCDebug(networking) << "Received network peer object for domain -" << packetPeer; - _icePeer = packetPeer; + qCDebug(networking) << "Received network peer object for domain -" << _icePeer; - emit requestICEConnectionAttempt(); + // ask the peer object to start its ping timer + _icePeer.startPingTimer(); + + // emit our signal so the NodeList knows to send a ping immediately + emit icePeerSocketsReceived(); } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 80a211405b..0c1698f5ec 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -85,7 +85,9 @@ public slots: private slots: void completedHostnameLookup(const QHostInfo& hostInfo); + void completedIceServerHostnameLookup(); void settingsRequestFinished(); + signals: void hostnameChanged(const QString& hostname); @@ -96,7 +98,8 @@ signals: void connectedToDomain(const QString& hostname); void disconnectedFromDomain(); - void requestICEConnectionAttempt(); + void iceSocketAndIDReceived(); + void icePeerSocketsReceived(); void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 34e0576e81..aa0ab262d7 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -61,6 +61,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short firstCall = false; } + qRegisterMetaType("ConnectionStep"); + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort(); @@ -470,7 +472,8 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez) { + bool canAdjustLocks, bool canRez, + const QUuid& connectionSecret) { NodeHash::const_iterator it = _nodeHash.find(uuid); if (it != _nodeHash.end()) { @@ -480,11 +483,17 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t matchingNode->setLocalSocket(localSocket); matchingNode->setCanAdjustLocks(canAdjustLocks); matchingNode->setCanRez(canRez); + matchingNode->setConnectionSecret(connectionSecret); return matchingNode; } else { // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez); + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez, connectionSecret, this); + + if (nodeType == NodeType::AudioMixer) { + LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); + } + SharedNodePointer newNodePointer(newNode); _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); @@ -596,6 +605,22 @@ const int NUM_BYTES_STUN_HEADER = 20; void LimitedNodeList::sendSTUNRequest() { + static quint64 lastTimeStamp = usecTimestampNow(); + lastTimeStamp = usecTimestampNow(); + + const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; + + if (!_hasCompletedInitialSTUN) { + qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; + + if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { + // we're still trying to do our initial STUN we're over the fail threshold + stopInitialSTUNUpdate(false); + } + + ++_numInitialSTUNRequests; + } + unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; int packetIndex = 0; @@ -620,6 +645,8 @@ void LimitedNodeList::sendSTUNRequest() { QUuid randomUUID = QUuid::createUuid(); memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); + flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr.getAddress(), _stunSockAddr.getPort()); } @@ -652,8 +679,6 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { const int NUM_BYTES_FAMILY_ALIGN = 1; const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; - - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; uint8_t addressFamily = 0; @@ -685,7 +710,14 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), _publicSockAddr.getPort()); + if (!_hasCompletedInitialSTUN) { + // if we're here we have definitely completed our initial STUN sequence + stopInitialSTUNUpdate(true); + } + emit publicSockAddrChanged(_publicSockAddr); + + flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } return true; @@ -707,6 +739,65 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { return false; } +void LimitedNodeList::startSTUNPublicSocketUpdate() { + assert(!_initialSTUNTimer); + + if (!_initialSTUNTimer) { + // if we don't know the STUN IP yet we need to have ourselves be called once it is known + if (_stunSockAddr.getAddress().isNull()) { + connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::startSTUNPublicSocketUpdate); + } else { + // setup our initial STUN timer here so we can quickly find out our public IP address + _initialSTUNTimer = new QTimer(this); + + connect(_initialSTUNTimer.data(), &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); + + const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250; + _initialSTUNTimer->start(STUN_INITIAL_UPDATE_INTERVAL_MSECS); + + // send an initial STUN request right away + sendSTUNRequest(); + } + } +} + +void LimitedNodeList::stopInitialSTUNUpdate(bool success) { + _hasCompletedInitialSTUN = true; + + if (!success) { + // if we're here this was the last failed STUN request + // use our DS as our stun server + qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu.", + STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + qCDebug(networking) << "LimitedNodeList public socket will be set with local port and null QHostAddress."; + + // reset the public address and port to a null address + _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); + + // we have changed the publicSockAddr, so emit our signal + emit publicSockAddrChanged(_publicSockAddr); + + flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); + } + + assert(_initialSTUNTimer); + + // stop our initial fast timer + if (_initialSTUNTimer) { + _initialSTUNTimer->stop(); + _initialSTUNTimer->deleteLater(); + } + + // We now setup a timer here to fire every so often to check that our IP address has not changed. + // Or, if we failed - if will check if we can eventually get a public socket + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + + QTimer* stunOccasionalTimer = new QTimer(this); + connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); + + stunOccasionalTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); +} + void LimitedNodeList::updateLocalSockAddr() { HifiSockAddr newSockAddr(getLocalAddress(), _nodeSocket.localPort()); if (newSockAddr != _localSockAddr) { @@ -723,23 +814,30 @@ void LimitedNodeList::updateLocalSockAddr() { } } -void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, - QUuid headerID, const QUuid& connectionRequestID) { +void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) { + sendPacketToIceServer(PacketTypeIceServerHeartbeat, iceServerSockAddr, _sessionUUID); +} - if (headerID.isNull()) { - headerID = _sessionUUID; - } +void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, + const QUuid& peerID) { + sendPacketToIceServer(PacketTypeIceServerQuery, iceServerSockAddr, clientID, peerID); +} - QByteArray iceRequestByteArray = byteArrayWithUUIDPopulatedHeader(PacketTypeIceServerHeartbeat, headerID); +void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, + const QUuid& headerID, const QUuid& peerID) { + + QByteArray iceRequestByteArray = byteArrayWithUUIDPopulatedHeader(packetType, headerID); QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); iceDataStream << _publicSockAddr << _localSockAddr; - if (!connectionRequestID.isNull()) { - iceDataStream << connectionRequestID; + if (packetType == PacketTypeIceServerQuery) { + assert(!peerID.isNull()); + + iceDataStream << peerID; qCDebug(networking) << "Sending packet to ICE server to request connection info for peer with ID" - << uuidStringWithoutCurlyBraces(connectionRequestID); + << uuidStringWithoutCurlyBraces(peerID); } writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr); @@ -774,3 +872,41 @@ bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quin return true; } } + +void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep) { + QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", + Q_ARG(ConnectionStep, connectionStep), + Q_ARG(quint64, usecTimestampNow())); +} + +void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp) { + + if (connectionStep == ConnectionStep::LookupAddress) { + QWriteLocker writeLock(&_connectionTimeLock); + + // we clear the current times if the user just fired off a lookup + _lastConnectionTimes.clear(); + _areConnectionTimesComplete = false; + + _lastConnectionTimes[timestamp] = connectionStep; + } else if (!_areConnectionTimesComplete) { + QWriteLocker writeLock(&_connectionTimeLock); + + + // anything > than sending the first DS check should not come before the DS check in, so we drop those + // this handles the case where you lookup an address and get packets in the existing domain before changing domains + if (connectionStep > LimitedNodeList::ConnectionStep::SendDSCheckIn + && (_lastConnectionTimes.key(ConnectionStep::SendDSCheckIn) == 0 + || timestamp <= _lastConnectionTimes.key(ConnectionStep::SendDSCheckIn))) { + return; + } + + // if there is no time for existing step add a timestamp on the first call for each ConnectionStep + _lastConnectionTimes[timestamp] = connectionStep; + + // if this is a received audio packet we consider our connection times complete + if (connectionStep == ConnectionStep::ReceiveFirstAudioPacket) { + _areConnectionTimesComplete = true; + } + } +} diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 3401cd7ca8..94063f49b1 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -21,13 +21,14 @@ #include // not on windows, not needed for mac or windows #endif -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -59,8 +60,6 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; class HifiSockAddr; -typedef QSet NodeSet; - typedef QSharedPointer SharedNodePointer; Q_DECLARE_METATYPE(SharedNodePointer) @@ -79,8 +78,31 @@ namespace PingType { class LimitedNodeList : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - public: + + enum ConnectionStep { + LookupAddress = 1, + HandleAddress, + SendSTUNRequest, + SetPublicSocketFromSTUN, + SetICEServerHostname, + SetICEServerSocket, + SendICEServerQuery, + ReceiveDSPeerInformation, + SendPingsToDS, + SetDomainHostname, + SetDomainSocket, + SendDSCheckIn, + ReceiveDSList, + AddedAudioMixer, + SendAudioPing, + SetAudioMixerSocket, + SendAudioPacket, + ReceiveFirstAudioPacket + }; + + Q_ENUMS(ConnectionStep); + const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); @@ -128,7 +150,10 @@ public: SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez); + bool canAdjustLocks, bool canRez, + const QUuid& connectionSecret = QUuid()); + + bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } @@ -149,11 +174,10 @@ public: 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()); + void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); + void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); template void eachNode(NodeLambda functor) { @@ -202,6 +226,11 @@ public: void putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort); bool getLocalServerPortFromSharedMemory(const QString key, quint16& localPort); + const QMap getLastConnectionTimes() const + { QReadLocker readLock(&_connectionTimeLock); return _lastConnectionTimes; } + void flagTimeForConnectionStep(ConnectionStep connectionStep); + + public slots: void reset(); void eraseAllNodes(); @@ -210,6 +239,9 @@ public slots: void updateLocalSockAddr(); + void startSTUNPublicSocketUpdate(); + virtual void sendSTUNRequest(); + void killNodeWithUUID(const QUuid& nodeUUID); signals: void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); @@ -240,6 +272,11 @@ protected: void handleNodeKill(const SharedNodePointer& node); + void stopInitialSTUNUpdate(bool success); + + void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& headerID, + const QUuid& peerRequestID = QUuid()); + QUuid _sessionUUID; NodeHash _nodeHash; QReadWriteLock _nodeMutex; @@ -259,6 +296,16 @@ protected: std::unordered_map _packetSequenceNumbers; + QPointer _initialSTUNTimer; + int _numInitialSTUNRequests = 0; + bool _hasCompletedInitialSTUN = false; + quint64 _firstSTUNTime = 0; + quint64 _publicSocketUpdateTime = 0; + + mutable QReadWriteLock _connectionTimeLock { }; + QMap _lastConnectionTimes; + bool _areConnectionTimesComplete = false; + template void eachNodeHashIterator(IteratorLambda functor) { QWriteLocker writeLock(&_nodeMutex); @@ -268,7 +315,8 @@ protected: functor(it); } } - +private slots: + void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index de1b8f66ba..dfa4066dd2 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -9,61 +9,153 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "NetworkPeer.h" + +#include +#include #include #include -#include "NetworkPeer.h" +#include "NetworkLogging.h" + #include "BandwidthRecorder.h" -NetworkPeer::NetworkPeer() : +NetworkPeer::NetworkPeer(QObject* parent) : + QObject(parent), _uuid(), _publicSocket(), _localSocket(), + _symmetricSocket(), + _activeSocket(NULL), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) { - + } -NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : +NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent) : + QObject(parent), _uuid(uuid), _publicSocket(publicSocket), _localSocket(localSocket), + _symmetricSocket(), + _activeSocket(NULL), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) { - + } -NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) : QObject() { - _uuid = otherPeer._uuid; - _publicSocket = otherPeer._publicSocket; - _localSocket = otherPeer._localSocket; - - _wakeTimestamp = otherPeer._wakeTimestamp; - _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; - _connectionAttempts = otherPeer._connectionAttempts; +void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { + if (publicSocket != _publicSocket) { + if (_activeSocket == &_publicSocket) { + // if the active socket was the public socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_publicSocket.isNull()) { + qCDebug(networking) << "Public socket change for node" << *this; + } + + _publicSocket = publicSocket; + } } -NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) { - NetworkPeer temp(otherPeer); - swap(temp); - return *this; +void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { + if (localSocket != _localSocket) { + if (_activeSocket == &_localSocket) { + // if the active socket was the local socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_localSocket.isNull()) { + qCDebug(networking) << "Local socket change for node" << *this; + } + + _localSocket = localSocket; + } } -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); +void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { + if (symmetricSocket != _symmetricSocket) { + if (_activeSocket == &_symmetricSocket) { + // if the active socket was the symmetric socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_symmetricSocket.isNull()) { + qCDebug(networking) << "Symmetric socket change for node" << *this; + } + + _symmetricSocket = symmetricSocket; + } +} + +void NetworkPeer::setActiveSocket(HifiSockAddr* discoveredSocket) { + _activeSocket = discoveredSocket; + + // we have an active socket, stop our ping timer + stopPingTimer(); + + // we're now considered connected to this peer - reset the number of connection attemps + resetConnectionAttempts(); +} + +void NetworkPeer::activateLocalSocket() { + if (_activeSocket != &_localSocket) { + qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_localSocket); + } +} + +void NetworkPeer::activatePublicSocket() { + if (_activeSocket != &_publicSocket) { + qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_publicSocket); + } +} + +void NetworkPeer::activateSymmetricSocket() { + if (_activeSocket != &_symmetricSocket) { + qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_symmetricSocket); + } +} + +void NetworkPeer::activateMatchingOrNewSymmetricSocket(const HifiSockAddr& matchableSockAddr) { + if (matchableSockAddr == _publicSocket) { + activatePublicSocket(); + } else if (matchableSockAddr == _localSocket) { + activateLocalSocket(); + } else { + // set the Node's symmetric socket to the passed socket + setSymmetricSocket(matchableSockAddr); + activateSymmetricSocket(); + } +} + +void NetworkPeer::softReset() { + // a soft reset should clear the sockets and reset the number of connection attempts + _localSocket.clear(); + _publicSocket.clear(); + _symmetricSocket.clear(); + _activeSocket = NULL; + + // stop our ping timer since we don't have sockets to ping anymore anyways + stopPingTimer(); + + _connectionAttempts = 0; +} + +void NetworkPeer::reset() { + softReset(); + + _uuid = QUuid(); + _wakeTimestamp = QDateTime::currentMSecsSinceEpoch(); + _lastHeardMicrostamp = usecTimestampNow(); } QByteArray NetworkPeer::toByteArray() const { @@ -71,15 +163,33 @@ QByteArray NetworkPeer::toByteArray() const { QDataStream peerStream(&peerByteArray, QIODevice::Append); peerStream << *this; - + return peerByteArray; } +void NetworkPeer::startPingTimer() { + if (!_pingTimer) { + _pingTimer = new QTimer(this); + + connect(_pingTimer, &QTimer::timeout, this, &NetworkPeer::pingTimerTimeout); + + _pingTimer->start(UDP_PUNCH_PING_INTERVAL_MS); + } +} + +void NetworkPeer::stopPingTimer() { + if (_pingTimer) { + _pingTimer->stop(); + _pingTimer->deleteLater(); + _pingTimer = NULL; + } +} + QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) { out << peer._uuid; out << peer._publicSocket; out << peer._localSocket; - + return out; } @@ -87,7 +197,7 @@ QDataStream& operator>>(QDataStream& in, NetworkPeer& peer) { in >> peer._uuid; in >> peer._publicSocket; in >> peer._localSocket; - + return in; } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index e44fac2dcc..d2802a1308 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -12,8 +12,9 @@ #ifndef hifi_NetworkPeer_h #define hifi_NetworkPeer_h -#include -#include +#include +#include +#include #include "HifiSockAddr.h" @@ -22,59 +23,80 @@ const int ICE_SERVER_DEFAULT_PORT = 7337; const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; const int MAX_ICE_CONNECTION_ATTEMPTS = 5; +const int UDP_PUNCH_PING_INTERVAL_MS = 25; + class NetworkPeer : public QObject { + Q_OBJECT 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); - + NetworkPeer(QObject* parent = 0); + NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent = 0); + bool isNull() const { return _uuid.isNull(); } - + bool hasSockets() const { return !_localSocket.isNull() && !_publicSocket.isNull(); } + const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } - + + void softReset(); 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; } - + const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } + + void setPublicSocket(const HifiSockAddr& publicSocket); + void setLocalSocket(const HifiSockAddr& localSocket); + void setSymmetricSocket(const HifiSockAddr& symmetricSocket); + + const HifiSockAddr* getActiveSocket() const { return _activeSocket; } + + void activatePublicSocket(); + void activateLocalSocket(); + void activateSymmetricSocket(); + + void activateMatchingOrNewSymmetricSocket(const HifiSockAddr& matchableSockAddr); + 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; } + void resetConnectionAttempts() { _connectionAttempts = 0; } void recordBytesSent(int count); void recordBytesReceived(int count); float getOutboundBandwidth(); // in kbps float getInboundBandwidth(); // in kbps - + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); +public slots: + void startPingTimer(); + void stopPingTimer(); +signals: + void pingTimerTimeout(); protected: + void setActiveSocket(HifiSockAddr* discoveredSocket); + QUuid _uuid; - + HifiSockAddr _publicSocket; HifiSockAddr _localSocket; - + HifiSockAddr _symmetricSocket; + HifiSockAddr* _activeSocket; + quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; - + + QTimer* _pingTimer = NULL; + int _connectionAttempts; -private: - void swap(NetworkPeer& otherPeer); }; QDebug operator<<(QDebug debug, const NetworkPeer &peer); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index ddc0571db4..05da87d69a 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -16,7 +16,6 @@ #include "Node.h" #include "SharedUtil.h" -#include "NetworkLogging.h" #include #include @@ -42,12 +41,11 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez) : - NetworkPeer(uuid, publicSocket, localSocket), + const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez, const QUuid& connectionSecret, + QObject* parent) : + NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), - _activeSocket(NULL), - _symmetricSocket(), - _connectionSecret(), + _connectionSecret(connectionSecret), _linkedData(NULL), _isAlive(true), _pingMs(-1), // "Uninitialized" @@ -57,7 +55,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _canAdjustLocks(canAdjustLocks), _canRez(canRez) { - + } Node::~Node() { @@ -69,68 +67,8 @@ void Node::updateClockSkewUsec(int clockSkewSample) { _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); } -void Node::setPublicSocket(const HifiSockAddr& publicSocket) { - if (publicSocket != _publicSocket) { - if (_activeSocket == &_publicSocket) { - // if the active socket was the public socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_publicSocket.isNull()) { - qCDebug(networking) << "Public socket change for node" << *this; - } - - _publicSocket = publicSocket; - } -} - -void Node::setLocalSocket(const HifiSockAddr& localSocket) { - if (localSocket != _localSocket) { - if (_activeSocket == &_localSocket) { - // if the active socket was the local socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_localSocket.isNull()) { - qCDebug(networking) << "Local socket change for node" << *this; - } - - _localSocket = localSocket; - } -} - -void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { - if (symmetricSocket != _symmetricSocket) { - if (_activeSocket == &_symmetricSocket) { - // if the active socket was the symmetric socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_symmetricSocket.isNull()) { - qCDebug(networking) << "Symmetric socket change for node" << *this; - } - - _symmetricSocket = symmetricSocket; - } -} - -void Node::activateLocalSocket() { - qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_localSocket; -} - -void Node::activatePublicSocket() { - qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_publicSocket; -} - -void Node::activateSymmetricSocket() { - qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_symmetricSocket; -} - PacketSequenceNumber Node::getLastSequenceNumberForPacketType(PacketType packetType) const { - auto typeMatch = _lastSequenceNumbers.find(packetType); + auto typeMatch = _lastSequenceNumbers.find(packetType); if (typeMatch != _lastSequenceNumbers.end()) { return typeMatch->second; } else { @@ -145,7 +83,7 @@ QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._localSocket; out << node._canAdjustLocks; out << node._canRez; - + return out; } @@ -156,7 +94,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) { in >> node._localSocket; in >> node._canAdjustLocks; in >> node._canRez; - + return in; } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 0836a448b2..4c1b6e9d18 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -23,30 +23,18 @@ #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NodeData.h" +#include "NodeType.h" #include "PacketHeaders.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" -typedef quint8 NodeType_t; - -namespace NodeType { - const NodeType_t DomainServer = 'D'; - const NodeType_t EntityServer = 'o'; // was ModelServer - const NodeType_t EnvironmentServer = 'E'; - const NodeType_t Agent = 'I'; - const NodeType_t AudioMixer = 'M'; - const NodeType_t AvatarMixer = 'W'; - const NodeType_t Unassigned = 1; - - void init(); - const QString& getNodeTypeName(NodeType_t nodeType); -} - class Node : public NetworkPeer { Q_OBJECT -public: +public: Node(const QUuid& uuid, NodeType_t type, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez); + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, + bool canAdjustLocks, bool canRez, const QUuid& connectionSecret = QUuid(), + QObject* parent = 0); ~Node(); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -54,7 +42,7 @@ public: char getType() const { return _type; } void setType(char type) { _type = type; } - + const QUuid& getConnectionSecret() const { return _connectionSecret; } void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } @@ -70,28 +58,17 @@ public: int getClockSkewUsec() const { return _clockSkewUsec; } 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 setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; } bool getCanAdjustLocks() { return _canAdjustLocks; } void setCanRez(bool canRez) { _canRez = canRez; } bool getCanRez() { return _canRez; } - - void activatePublicSocket(); - void activateLocalSocket(); - void activateSymmetricSocket(); void setLastSequenceNumberForPacketType(PacketSequenceNumber sequenceNumber, PacketType packetType) { _lastSequenceNumbers[packetType] = sequenceNumber; } PacketSequenceNumber getLastSequenceNumberForPacketType(PacketType packetType) const; - + friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -101,10 +78,7 @@ private: Node& operator=(Node otherNode); NodeType_t _type; - - HifiSockAddr* _activeSocket; - HifiSockAddr _symmetricSocket; - + QUuid _connectionSecret; NodeData* _linkedData; bool _isAlive; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b39503f709..26cd3f1425 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include #include @@ -34,9 +36,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _nodeTypesOfInterest(), _domainHandler(this), _numNoReplyDomainCheckIns(0), - _assignmentServerSocket(), - _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0) + _assignmentServerSocket() { static bool firstCall = true; if (firstCall) { @@ -61,17 +61,35 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // fire off any pending DS path query when we get socket information connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendPendingDSPathQuery); + // send a domain server check in immediately once the DS socket is known + connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendDomainServerCheckIn); + + // send a domain server check in immediately if there is a public socket change + connect(this, &LimitedNodeList::publicSockAddrChanged, this, &NodeList::sendDomainServerCheckIn); + // clear our NodeList when the domain changes connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::reset); - // handle ICE signal from DS so connection is attempted immediately - connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer); + // send an ICE heartbeat as soon as we get ice server information + connect(&_domainHandler, &DomainHandler::iceSocketAndIDReceived, this, &NodeList::handleICEConnectionToDomainServer); + + // handle ping timeout from DomainHandler to establish a connection with auto networked domain-server + connect(&_domainHandler.getICEPeer(), &NetworkPeer::pingTimerTimeout, this, &NodeList::pingPunchForDomainServer); + + // send a ping punch immediately + connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); // clear out NodeList when login is finished connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + // anytime we get a new node we will want to attempt to punch to it + connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); + + // we definitely want STUN to update our public socket, so call the LNL to kick that off + startSTUNPublicSocketUpdate(); } qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination) { @@ -155,12 +173,18 @@ void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& } void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - switch (packetTypeForPacket(packet)) { - case PacketTypeDomainList: { + PacketType packetType = packetTypeForPacket(packet); + switch (packetType) { + case PacketTypeDomainList: + case PacketTypeDomainServerAddedNode: { if (!_domainHandler.getSockAddr().isNull()) { - // only process a list from domain-server if we're talking to a domain + // only process a packet from domain-server if we're talking to a domain // TODO: how do we make sure this is actually the domain we want the list from (DTLS probably) - processDomainServerList(packet); + if (packetType == PacketTypeDomainList) { + processDomainServerList(packet); + } else if (packetType == PacketTypeDomainServerAddedNode) { + processDomainServerAddedNode(packet); + } } break; } @@ -168,8 +192,10 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr _domainHandler.parseDTLSRequirementPacket(packet); break; } - case PacketTypeIceServerHeartbeatResponse: { - _domainHandler.processICEResponsePacket(packet); + case PacketTypeIceServerPeerInformation: { + if (!_domainHandler.getICEPeer().hasSockets()) { + _domainHandler.processICEResponsePacket(packet); + } break; } case PacketTypePing: { @@ -216,15 +242,18 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr case PacketTypeUnverifiedPingReply: { qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; - // for now we're unsafely assuming this came back from the domain - if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { - qCDebug(networking) << "Connecting to domain using local socket"; - _domainHandler.activateICELocalSocket(); - } else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) { - qCDebug(networking) << "Conecting to domain using public socket"; - _domainHandler.activateICEPublicSocket(); - } else { - qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; + if (_domainHandler.getIP().isNull()) { + // for now we're unsafely assuming this came back from the domain + if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { + qCDebug(networking) << "Connecting to domain using local socket"; + _domainHandler.activateICELocalSocket(); + } else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) { + qCDebug(networking) << "Conecting to domain using public socket"; + _domainHandler.activateICEPublicSocket(); + } else { + qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; + } + } } case PacketTypeStunResponse: { @@ -270,57 +299,14 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) _nodeTypesOfInterest.unite(setOfNodeTypes); } - -const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; - -void NodeList::sendSTUNRequest() { - - if (!_hasCompletedInitialSTUNFailure) { - qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; - } - - LimitedNodeList::sendSTUNRequest(); - - _stunRequestsSinceSuccess++; - - if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { - if (!_hasCompletedInitialSTUNFailure) { - // if we're here this was the last failed STUN request - // use our DS as our stun server - qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", - STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - _hasCompletedInitialSTUNFailure = true; - } - - // reset the public address and port - // use 0 so the DS knows to act as out STUN server - _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); - } -} - -bool NodeList::processSTUNResponse(const QByteArray& packet) { - if (LimitedNodeList::processSTUNResponse(packet)) { - // reset the number of failed STUN requests since last success - _stunRequestsSinceSuccess = 0; - - _hasCompletedInitialSTUNFailure = true; - - return true; - } else { - return false; - } -} - void NodeList::sendDomainServerCheckIn() { - if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { + if (_publicSockAddr.isNull()) { // 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(); + qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { + qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { - bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() @@ -365,7 +351,6 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); - // if this is a connect request, and we can present a username signature, send it along if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); @@ -379,18 +364,12 @@ void NodeList::sendDomainServerCheckIn() { } } + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); + if (!isUsingDTLS) { writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr()); } - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that @@ -499,17 +478,40 @@ void NodeList::handleDSPathQueryResponse(const QByteArray& packet) { } void NodeList::handleICEConnectionToDomainServer() { - if (_domainHandler.getICEPeer().isNull() - || _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { + // if we're still waiting to get sockets we want to ping for the domain-server + // then send another heartbeat now + if (!_domainHandler.getICEPeer().hasSockets()) { - _domainHandler.getICEPeer().resetConnectionAttemps(); + _domainHandler.getICEPeer().resetConnectionAttempts(); - LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerQuery); + + LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getICEDomainID()); - } else { - qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + } +} + +void NodeList::pingPunchForDomainServer() { + // make sure if we're here that we actually still need to ping the domain-server + if (_domainHandler.getIP().isNull() && _domainHandler.getICEPeer().hasSockets()) { + + // check if we've hit the number of pings we'll send to the DS before we consider it a fail + const int NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET = 2000 / UDP_PUNCH_PING_INTERVAL_MS; + + if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { + qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + } else { + if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { + // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat + + _domainHandler.getICEPeer().softReset(); + handleICEConnectionToDomainServer(); + } + } + + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); @@ -526,6 +528,8 @@ int NodeList::processDomainServerList(const QByteArray& packet) { // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); + // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { _domainHandler.setUUID(uuidFromPacketHeader(packet)); @@ -551,36 +555,45 @@ int NodeList::processDomainServerList(const QByteArray& packet) { setThisNodeCanRez(thisNodeCanRez); // pull each node in the packet - while(packetStream.device()->pos() < packet.size()) { - // setup variables to read into from QDataStream - qint8 nodeType; - QUuid nodeUUID, connectionUUID; - HifiSockAddr nodePublicSocket, nodeLocalSocket; - bool canAdjustLocks; - bool canRez; - - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks >> canRez; - - // if the public socket address is 0 then it's reachable at the same IP - // as the domain server - if (nodePublicSocket.getAddress().isNull()) { - nodePublicSocket.setAddress(_domainHandler.getIP()); - } - - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, - nodeLocalSocket, canAdjustLocks, canRez); - - packetStream >> connectionUUID; - node->setConnectionSecret(connectionUUID); + while (packetStream.device()->pos() < packet.size()) { + parseNodeFromPacketStream(packetStream); } - // ping inactive nodes in conjunction with receipt of list from domain-server - // this makes it happen every second and also pings any newly added nodes - pingInactiveNodes(); - return readNodes; } +void NodeList::processDomainServerAddedNode(const QByteArray& packet) { + // setup a QDataStream, skip the header + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + // use our shared method to pull out the new node + parseNodeFromPacketStream(packetStream); +} + +void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { + // setup variables to read into from QDataStream + qint8 nodeType; + QUuid nodeUUID, connectionUUID; + HifiSockAddr nodePublicSocket, nodeLocalSocket; + bool canAdjustLocks; + bool canRez; + + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks >> canRez; + + // if the public socket address is 0 then it's reachable at the same IP + // as the domain server + if (nodePublicSocket.getAddress().isNull()) { + nodePublicSocket.setAddress(_domainHandler.getIP()); + } + + packetStream >> connectionUUID; + + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, + nodeLocalSocket, canAdjustLocks, canRez, + connectionUUID); +} + void NodeList::sendAssignment(Assignment& assignment) { PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand @@ -596,6 +609,16 @@ void NodeList::sendAssignment(Assignment& assignment) { } void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + if (node->getType() == NodeType::AudioMixer) { + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); + } + + // every second we're trying to ping this node and we're not getting anywhere - debug that out + const int NUM_DEBUG_CONNECTION_ATTEMPTS = 1000 / (UDP_PUNCH_PING_INTERVAL_MS); + + if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) { + qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second."; + } // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local); @@ -608,15 +631,30 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); } + + node->incrementConnectionAttempts(); } -void NodeList::pingInactiveNodes() { - eachNode([this](const SharedNodePointer& node){ - if (!node->getActiveSocket()) { - // we don't have an active link to this node, ping it to set that up - pingPunchForInactiveNode(node); +void NodeList::startNodeHolePunch(const SharedNodePointer& node) { + // connect to the correct signal on this node so we know when to ping it + connect(node.data(), &Node::pingTimerTimeout, this, &NodeList::handleNodePingTimeout); + + // start the ping timer for this node + node->startPingTimer(); + + // ping this node immediately + pingPunchForInactiveNode(node); +} + +void NodeList::handleNodePingTimeout() { + Node* senderNode = qobject_cast(sender()); + if (senderNode) { + SharedNodePointer sharedNode = nodeWithUUID(senderNode->getUUID()); + + if (sharedNode && !sharedNode->getActiveSocket()) { + pingPunchForInactiveNode(sharedNode); } - }); + } } void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { @@ -636,4 +674,8 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { sendingNode->activateSymmetricSocket(); } + + if (sendingNode->getType() == NodeType::AudioMixer) { + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetAudioMixerSocket); + } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 39b1e3e2d2..5e70b45b5f 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -61,32 +61,29 @@ public: void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); - int processDomainServerList(const QByteArray& packet); - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); - void pingPunchForInactiveNode(const SharedNodePointer& node); public slots: void reset(); void sendDomainServerCheckIn(); - void pingInactiveNodes(); void handleDSPathQuery(const QString& newPath); signals: void limitOfSilentDomainCheckInsReached(); private slots: void sendPendingDSPathQuery(); + void handleICEConnectionToDomainServer(); + + void startNodeHolePunch(const SharedNodePointer& node); + void handleNodePingTimeout(); + + void pingPunchForDomainServer(); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton - void sendSTUNRequest(); - bool processSTUNResponse(const QByteArray& packet); - - void handleICEConnectionToDomainServer(); - void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); @@ -96,13 +93,17 @@ private: void sendDSPathQuery(const QString& newPath); + int processDomainServerList(const QByteArray& packet); + void processDomainServerAddedNode(const QByteArray& packet); + void parseNodeFromPacketStream(QDataStream& packetStream); + + void pingPunchForInactiveNode(const SharedNodePointer& node); + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; - bool _hasCompletedInitialSTUNFailure; - unsigned int _stunRequestsSinceSuccess; friend class Application; }; diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h new file mode 100644 index 0000000000..4427b87158 --- /dev/null +++ b/libraries/networking/src/NodeType.h @@ -0,0 +1,34 @@ +// +// NodeType.h +// libraries/networking/src +// +// Created by Stephen Birarda on 05/29/15. +// Copyright 2015 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_NodeType_h +#define hifi_NodeType_h + +#pragma once + +typedef quint8 NodeType_t; + +namespace NodeType { + const NodeType_t DomainServer = 'D'; + const NodeType_t EntityServer = 'o'; // was ModelServer + const NodeType_t EnvironmentServer = 'E'; + const NodeType_t Agent = 'I'; + const NodeType_t AudioMixer = 'M'; + const NodeType_t AvatarMixer = 'W'; + const NodeType_t Unassigned = 1; + + void init(); + const QString& getNodeTypeName(NodeType_t nodeType); +} + +typedef QSet NodeSet; + +#endif // hifi_NodeType_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index c5030fb1e7..4b02a32cad 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,6 +78,9 @@ PacketVersion versionForPacketType(PacketType packetType) { return 2; case PacketTypeAudioStreamStats: return 1; + case PacketTypeIceServerHeartbeat: + case PacketTypeIceServerQuery: + return 1; default: return 0; } @@ -125,7 +128,9 @@ QString nameForPacketType(PacketType packetType) { PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityEditNack); PACKET_TYPE_NAME_LOOKUP(PacketTypeSignedTransactionPayment); PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeat); - PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeatResponse); + PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainServerAddedNode); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerQuery); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerPeerInformation); PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPing); PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPingReply); PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityAdd); @@ -149,33 +154,33 @@ int populatePacketHeaderWithUUID(QByteArray& packet, PacketType packetType, cons if (packet.size() < numBytesForPacketHeaderGivenPacketType(packetType)) { packet.resize(numBytesForPacketHeaderGivenPacketType(packetType)); } - + return populatePacketHeaderWithUUID(packet.data(), packetType, connectionUUID); } int populatePacketHeaderWithUUID(char* packet, PacketType packetType, const QUuid& connectionUUID) { int numTypeBytes = packArithmeticallyCodedValue(packetType, packet); packet[numTypeBytes] = versionForPacketType(packetType); - + char* position = packet + numTypeBytes + sizeof(PacketVersion); - + QByteArray rfcUUID = connectionUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); position += NUM_BYTES_RFC4122_UUID; - + if (!NON_VERIFIED_PACKETS.contains(packetType)) { // pack 16 bytes of zeros where the md5 hash will be placed once data is packed memset(position, 0, NUM_BYTES_MD5_HASH); position += NUM_BYTES_MD5_HASH; } - + if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) { // Pack zeros for the number of bytes that the sequence number requires. // The LimitedNodeList will handle packing in the sequence number when sending out the packet. memset(position, 0, sizeof(PacketSequenceNumber)); position += sizeof(PacketSequenceNumber); } - + // return the number of bytes written for pointer pushing return position - packet; } @@ -235,13 +240,13 @@ PacketSequenceNumber sequenceNumberFromHeader(const QByteArray& packet, PacketTy if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + PacketSequenceNumber result = DEFAULT_SEQUENCE_NUMBER; - + if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) { memcpy(&result, packet.data() + sequenceNumberOffsetForPacketType(packetType), sizeof(PacketSequenceNumber)); } - + return result; } @@ -249,7 +254,7 @@ void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, Packet if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + packet.replace(hashOffsetForPacketType(packetType), NUM_BYTES_MD5_HASH, hashForPacketAndConnectionUUID(packet, connectionUUID)); } @@ -258,7 +263,7 @@ void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequ if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + packet.replace(sequenceNumberOffsetForPacketType(packetType), sizeof(PacketSequenceNumber), reinterpret_cast(&sequenceNumber), sizeof(PacketSequenceNumber)); } @@ -268,7 +273,7 @@ void replaceHashAndSequenceNumberInPacket(QByteArray& packet, const QUuid& conne if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + replaceHashInPacket(packet, connectionUUID, packetType); replaceSequenceNumberInPacket(packet, sequenceNumber, packetType); } diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 8b952e6eb3..cf1a323741 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -25,6 +25,7 @@ // NOTE: if adding a new packet packetType, you can replace one marked usable or add at the end // NOTE: if you want the name of the packet packetType to be available for debugging or logging, update nameForPacketType() as well + enum PacketType { PacketTypeUnknown, // 0 PacketTypeStunResponse, @@ -49,9 +50,9 @@ enum PacketType { PacketTypeDataServerConfirm, // 20 PacketTypeDomainServerPathQuery, PacketTypeDomainServerPathResponse, - UNUSED_3, - UNUSED_4, - UNUSED_5, // 25 + PacketTypeDomainServerAddedNode, + PacketTypeIceServerPeerInformation, + PacketTypeIceServerQuery, // 25 PacketTypeOctreeStats, PacketTypeJurisdiction, PacketTypeJurisdictionRequest, @@ -77,7 +78,6 @@ enum PacketType { PacketTypeEntityEditNack, PacketTypeSignedTransactionPayment, PacketTypeIceServerHeartbeat, // 50 - PacketTypeIceServerHeartbeatResponse, PacketTypeUnverifiedPing, PacketTypeUnverifiedPingReply, PacketTypeParticleEntitiesFix @@ -91,14 +91,16 @@ const PacketSequenceNumber DEFAULT_SEQUENCE_NUMBER = 0; typedef std::map PacketTypeSequenceMap; const QSet NON_VERIFIED_PACKETS = QSet() -<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest -<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied -<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse -<< PacketTypeNodeJsonStats << PacketTypeEntityQuery -<< PacketTypeOctreeDataNack << PacketTypeEntityEditNack -<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse -<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode -<< PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse; + << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest + << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied + << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse + << PacketTypeNodeJsonStats << PacketTypeEntityQuery + << PacketTypeOctreeDataNack << PacketTypeEntityEditNack + << PacketTypeIceServerHeartbeat << PacketTypeIceServerPeerInformation + << PacketTypeIceServerQuery << PacketTypeUnverifiedPing + << PacketTypeUnverifiedPingReply << PacketTypeStopNode + << PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse + << PacketTypeDomainServerAddedNode; const QSet SEQUENCE_NUMBERED_PACKETS = QSet() << PacketTypeAvatarData; diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 676d1a04ce..e2e0aa0fed 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -46,7 +46,6 @@ protected: private slots: void checkInWithDomainServerOrExit(); - }; typedef QSharedPointer SharedAssignmentPointer;