Merge pull request #4995 from birarda/domain-dtls

speed up initial connection to domains
This commit is contained in:
Seth Alves 2015-05-29 10:50:01 -07:00
commit b0f6826d17
28 changed files with 1295 additions and 778 deletions

View file

@ -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<LimitedNodeList>();
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<DomainServerNodeData*>(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<LimitedNodeList>();
@ -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<LimitedNodeList>();
QByteArray broadcastPacket = limitedNodeList->byteArrayWithPopulatedHeader(PacketTypeDomainList);
@ -945,14 +933,10 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(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<DomainServerNodeData*>(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<DomainServerNodeData*>(nodeA->getLinkedData());
DomainServerNodeData* nodeBData = dynamic_cast<DomainServerNodeData*>(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<DomainServerNodeData*>(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID);
}
return secretUUID;
}
return QUuid();
}
void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
// 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<DomainServerNodeData*>(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<LimitedNodeList>()->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<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
}
void DomainServer::sendICEPingPackets() {
auto nodeList = DependencyManager::get<LimitedNodeList>();
const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS;
QHash<QUuid, NetworkPeer>::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<LimitedNodeList>();
// 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<NetworkPeer*>(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<DomainServerNodeData*>(node->getLinkedData());

View file

@ -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<Assignment::Type>& excludedTypes);
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
@ -151,8 +155,7 @@ private:
QHash<QString, QByteArray> _userPublicKeys;
QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
QHash<QUuid, SharedNetworkPeer> _icePeers;
QString _automaticNetworkingSetting;

View file

@ -12,44 +12,47 @@
#ifndef hifi_DomainServerNodeData_h
#define hifi_DomainServerNodeData_h
#include <QtCore/QElapsedTimer>
#include <QtCore/QHash>
#include <QtCore/QUuid>
#include <HifiSockAddr.h>
#include <NodeData.h>
#include <NodeType.h>
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<QUuid, QUuid>& 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<QUuid, QUuid> _sessionSecretHash;
QUuid _assignmentUUID;
QUuid _walletUUID;
@ -58,6 +61,7 @@ private:
QJsonObject _statsJSONObject;
HifiSockAddr _sendingSockAddr;
bool _isAuthenticated;
NodeSet _nodeInterestSet;
};
#endif // hifi_DomainServerNodeData_h

View file

@ -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;
}

View file

@ -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<QUuid>& requestingConnections = _currentConnections[senderUUID];
if (!connectRequestID.isNull()) {
qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID);
// ensure this peer is in the set of current connections for the peer with ID it wants to connect with
_currentConnections[connectRequestID].insert(senderUUID);
// add the ID of the node they have said they would like to connect to
requestingConnections.insert(connectRequestID);
}
if (requestingConnections.size() > 0) {
// send a heartbeart response based on the set of connections
qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size()
<< "potential connections";
sendHeartbeatResponse(sendingSockAddr, requestingConnections);
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<QUuid>& connections) {
QSet<QUuid>::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()));

View file

@ -31,14 +31,14 @@ private slots:
void processDatagrams();
void clearInactivePeers();
private:
void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet<QUuid>& connections);
SharedNetworkPeer addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket);
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
QUuid _id;
QUdpSocket _serverSocket;
NetworkPeerHash _activePeers;
QHash<QUuid, QSet<QUuid> > _currentConnections;
HTTPManager _httpManager;
};
#endif // hifi_IceServer_h
#endif // hifi_IceServer_h

View file

@ -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<GlowEffect>().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<AvatarManager>();
auto avatarManager = DependencyManager::get<AvatarManager>();
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<AudioScope>();
MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope");
@ -548,7 +552,7 @@ Menu::Menu() {
audioScopeFramesGroup->addAction(twentyFrames);
audioScopeFramesGroup->addAction(fiftyFrames);
}
auto statsRenderer = DependencyManager::get<AudioIOStatsRenderer>();
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);

View file

@ -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<QString, QAction*> _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";

View file

@ -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();
}

View file

@ -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<BandwidthDialog> getBandwidthDialog() const { return _bandwidthDialog; }
QPointer<HMDToolsDialog> getHMDToolsDialog() const { return _hmdToolsDialog; }
QPointer<LodToolsDialog> getLodToolsDialog() const { return _lodToolsDialog; }
QPointer<OctreeStatsDialog> getOctreeStatsDialog() const { return _octreeStatsDialog; }
QPointer<PreferencesDialog> 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<typename T>
void maybeCreateDialog(QPointer<T>& 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> _addressBarDialog;
QPointer<AnimationsDialog> _animationsDialog;
QPointer<AttachmentsDialog> _attachmentsDialog;
@ -98,6 +100,7 @@ private:
QPointer<PreferencesDialog> _preferencesDialog;
QPointer<ScriptEditorWindow> _scriptEditor;
QPointer<AvatarAppearanceDialog> _avatarAppearanceDialog;
QPointer<DomainConnectionDialog> _domainConnectionDialog;
};
#endif // hifi_DialogsManager_h
#endif // hifi_DialogsManager_h

View file

@ -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 <QtCore/QMetaEnum>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QTableWidget>
#include <NodeList.h>
#include <NumericalConstants.h>
#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<quint64, LimitedNodeList::ConnectionStep> times = DependencyManager::get<NodeList>()->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();
}

View file

@ -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 <QtWidgets/QDialog>
class DomainConnectionDialog : public QDialog {
Q_OBJECT
public:
DomainConnectionDialog(QWidget* parent);
};
#endif // hifi_DomainConnectionDialog_h

View file

@ -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<int16_t*>(inputByteArray.data());
int16_t* reverbAloneSamples = reinterpret_cast<int16_t*>(reverbAlone.data());
int16_t* loopbackSamples = reinterpret_cast<int16_t*>(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<const char*>(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<NodeList>();
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<const int16_t*>(inputBuffer.data());
// copy the packet from the RB to the output
possibleResampling(_networkToOutputResampler, receivedSamples,
reinterpret_cast<int16_t*>(outputBuffer.data()),
@ -900,20 +902,20 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr
void AudioClient::sendMuteEnvironmentPacket() {
auto nodeList = DependencyManager::get<NodeList>();
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, &currentSourcePosition, 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<NodeList>()->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);

View file

@ -106,6 +106,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
DependencyManager::get<NodeList>()->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<NodeList>()->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<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
emit possibleDomainChangeRequired(hostname, port);
}

View file

@ -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<NodeList>()->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<NodeList>()->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>();
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<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
_sockAddr = _icePeer.getLocalSocket();
_hostname = _sockAddr.getAddress().toString();
emit completedSocketDiscovery();
}
void DomainHandler::activateICEPublicSocket() {
DependencyManager::get<NodeList>()->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<NodeList>()->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<NodeList>()->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<NodeList>()->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();
}
}

View file

@ -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();

View file

@ -61,6 +61,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
firstCall = false;
}
qRegisterMetaType<ConnectionStep>("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;
}
}
}

View file

@ -21,13 +21,14 @@
#include <unistd.h> // not on windows, not needed for mac or windows
#endif
#include <qelapsedtimer.h>
#include <qreadwritelock.h>
#include <qset.h>
#include <qsharedpointer.h>
#include <QtNetwork/qudpsocket.h>
#include <QtNetwork/qhostaddress.h>
#include <QSharedMemory>
#include <QtCore/QElapsedTimer>
#include <QtCore/QPointer>
#include <QtCore/QReadWriteLock>
#include <QtCore/QSet>
#include <QtCore/QSharedMemory>
#include <QtCore/QSharedPointer>
#include <QtNetwork/QUdpSocket>
#include <QtNetwork/QHostAddress>
#include <tbb/concurrent_unordered_map.h>
@ -59,8 +60,6 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
class HifiSockAddr;
typedef QSet<NodeType_t> NodeSet;
typedef QSharedPointer<Node> 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<typename NodeLambda>
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<quint64, ConnectionStep> 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<QUuid, PacketTypeSequenceMap, UUIDHasher> _packetSequenceNumbers;
QPointer<QTimer> _initialSTUNTimer;
int _numInitialSTUNRequests = 0;
bool _hasCompletedInitialSTUN = false;
quint64 _firstSTUNTime = 0;
quint64 _publicSocketUpdateTime = 0;
mutable QReadWriteLock _connectionTimeLock { };
QMap<quint64, ConnectionStep> _lastConnectionTimes;
bool _areConnectionTimesComplete = false;
template<typename IteratorLambda>
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

View file

@ -9,61 +9,153 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <qdatetime.h>
#include "NetworkPeer.h"
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <SharedUtil.h>
#include <UUID.h>
#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;
}

View file

@ -12,8 +12,9 @@
#ifndef hifi_NetworkPeer_h
#define hifi_NetworkPeer_h
#include <qobject.h>
#include <quuid.h>
#include <QtCore/QObject>
#include <QtCore/QTimer>
#include <QtCore/QUuid>
#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);

View file

@ -16,7 +16,6 @@
#include "Node.h"
#include "SharedUtil.h"
#include "NetworkLogging.h"
#include <QtCore/QDataStream>
#include <QtCore/QDebug>
@ -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;
}

View file

@ -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;

View file

@ -12,7 +12,9 @@
#include <QtCore/QDataStream>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QMetaEnum>
#include <QtCore/QUrl>
#include <QtCore/QThread>
#include <QtNetwork/QHostInfo>
#include <LogHandler.h>
@ -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<NodeList>()->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<Node*>(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);
}
}

View file

@ -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;
};

View file

@ -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<NodeType_t> NodeSet;
#endif // hifi_NodeType_h

View file

@ -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<char*>(&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);
}

View file

@ -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<PacketType, PacketSequenceNumber> PacketTypeSequenceMap;
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< 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<PacketType> SEQUENCE_NUMBERED_PACKETS = QSet<PacketType>()
<< PacketTypeAvatarData;

View file

@ -46,7 +46,6 @@ protected:
private slots:
void checkInWithDomainServerOrExit();
};
typedef QSharedPointer<ThreadedAssignment> SharedAssignmentPointer;