Merge branch 'master' of github.com:highfidelity/hifi into no-id-swap-redux

This commit is contained in:
Seth Alves 2015-05-21 09:20:56 -07:00
commit 52ce26d80d
19 changed files with 897 additions and 775 deletions

View file

@ -43,8 +43,7 @@ int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
quint16 assignmentMonitorPort) :
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME),
_localASPortSharedMem(NULL)
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME)
{
LogUtils::init();
@ -181,8 +180,7 @@ void AssignmentClient::sendAssignmentRequest() {
if (_assignmentServerHostname == "localhost") {
// we want to check again for the local domain-server port in case the DS has restarted
quint16 localAssignmentServerPort;
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, _localASPortSharedMem,
localAssignmentServerPort)) {
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
qDebug() << "Port for local assignment server read from shared memory is"
<< localAssignmentServerPort;
@ -190,7 +188,11 @@ void AssignmentClient::sendAssignmentRequest() {
_assignmentServerSocket.setPort(localAssignmentServerPort);
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
}
} else {
qDebug() << "Failed to read local assignment server port from shared memory"
<< "- will send assignment request to previous assignment server socket.";
}
}
nodeList->sendAssignment(_requestAssignment);

View file

@ -44,7 +44,6 @@ private:
QPointer<ThreadedAssignment> _currentAssignment;
QString _assignmentServerHostname;
HifiSockAddr _assignmentServerSocket;
QSharedMemory* _localASPortSharedMem; // memory shared with domain server
QTimer _requestTimer; // timer for requesting and assignment
QTimer _statsTimerACM; // timer for sending stats to assignment client monitor

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -109,6 +109,13 @@ table {
width: 100%;
}
/* styling for bootstrap-switch toggles */
.checkbox-help {
margin-top: 10px;
}
/* CSS only spinner for AJAX requests */
.spinner {
margin: 30px auto 0;
width: 70px;

View file

@ -7,7 +7,9 @@
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/css/style.css" rel="stylesheet" media="screen">
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default" role="navigation">

View file

@ -99,7 +99,8 @@
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sweetalert.min.js'></script>
<script src='/js/settings.js'></script>
<script src='/js/form2js.min.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='js/sweetalert.min.js'></script>
<script src='js/settings.js'></script>
<script src='js/form2js.min.js'></script>
<!--#include virtual="page-end.html"-->

File diff suppressed because one or more lines are too long

View file

@ -60,10 +60,15 @@ var viewHelpers = {
if (setting.label) {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
}
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + keypath + "'>"
form_group += "<input type='checkbox'" + common_attrs() + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
form_group += " " + setting.help + "</label>";
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
form_group += (isLocked ? " disabled" : "") + "/>"
if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
}
form_group += "</div>"
} else {
input_type = _.has(setting, 'type') ? setting.type : "text"
@ -201,10 +206,17 @@ $(document).ready(function(){
$('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
// this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true)
$(this).attr('data-changed', true);
badgeSidebarForDifferences($(this))
})
badgeSidebarForDifferences($(this));
});
$('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){
// this checkbox was changed, add the changed data attribute to it
$(this).attr('data-changed', true);
badgeSidebarForDifferences($(this));
});
$('.advanced-toggle').click(function(){
Settings.showAdvanced = !Settings.showAdvanced
@ -735,6 +747,9 @@ function reloadSettings() {
// call our method to setup the place names table
setupPlacesTable();
// setup any bootstrap switches
$('.toggle-checkbox').bootstrapSwitch();
// add tooltip to locked settings
$('label.locked').tooltip({
placement: 'right',

View file

@ -44,13 +44,10 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923;
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
@ -776,9 +773,8 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const HifiSockAddr& senderSockAddr,
QString& reasonReturn) {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
bool isRestrictingAccess =
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
// we always let in a user who is sending a packet from our local socket or from the localhost address
if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
@ -786,45 +782,50 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
return true;
}
if (allowedUsers.count() > 0) {
if (isRestrictingAccess) {
QStringList allowedUsers =
_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
if (!verifyUsersKey(username, usernameSignature, reasonReturn)) {
return false;
}
} else {
qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
reasonReturn = "User not on whitelist.";
}
return false;
} else {
// we have no allowed user list.
// if this user is in the editors list, exempt them from the max-capacity check
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
return false;
}
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
}
// either we aren't restricting users, or this user is in the allowed list
// if this user is in the editors list, exempt them from the max-capacity check
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
}
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
}
void DomainServer::preloadAllowedUserPublicKeys() {
@ -1255,10 +1256,8 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
const QString RESTRICTED_ACCESS_FLAG = "restricted";
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0);
domainObject[RESTRICTED_ACCESS_FLAG] =
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;

View file

@ -14,6 +14,7 @@
#include <QtCore/QFile>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
@ -42,33 +43,73 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly);
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll());
if (descriptionDocument.isObject()) {
QJsonObject descriptionObject = descriptionDocument.object();
const QString DESCRIPTION_VERSION_KEY = "version";
if (descriptionObject.contains(DESCRIPTION_VERSION_KEY)) {
// read the version from the settings description
_descriptionVersion = descriptionObject[DESCRIPTION_VERSION_KEY].toDouble();
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
return;
}
}
}
qCritical() << "Did not find settings decription in JSON at" << SETTINGS_DESCRIPTION_RELATIVE_PATH
<< "- Unable to continue. domain-server will quit.";
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
}
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList);
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
// What settings version were we before and what are we using now?
// Do we need to do any re-mapping?
QSettings appSettings;
const QString JSON_SETTINGS_VERSION_KEY = "json-settings/version";
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
if (oldUsername || oldPassword) {
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data());
if (oldVersion != _descriptionVersion) {
qDebug() << "Previous domain-server settings version was"
<< QString::number(oldVersion, 'g', 8) << "and the new version is"
<< QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required";
// remove old keys, move to new format
if (oldUsername) {
settingsMap["http_username"] = oldUsername->toString();
settingsMap.remove("http-username");
// we have a version mismatch - for now handle custom behaviour here since there are not many remappings
if (oldVersion < 1.0) {
// This was prior to the introduction of security.restricted_access
// If the user has a list of allowed users then set their value for security.restricted_access to true
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH);
if (allowedUsers
&& allowedUsers->canConvert(QMetaType::QVariantList)
&& reinterpret_cast<QVariantList*>(allowedUsers)->size() > 0) {
qDebug() << "Forcing security.restricted_access to TRUE since there was an"
<< "existing list of allowed users.";
// In the pre-toggle system the user had a list of allowed users, so
// we need to set security.restricted_access to true
QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(),
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
*restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
}
}
if (oldPassword) {
settingsMap["http_password"] = oldPassword->toString();
settingsMap.remove("http-password");
}
// save the updated settings
persistToFile();
}
// write the current description version to our settings
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {

View file

@ -23,6 +23,9 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
@ -44,6 +47,7 @@ private:
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void persistToFile();
double _descriptionVersion;
QJsonArray _descriptionArray;
HifiConfigVariantMap _configMap;
};

View file

@ -55,46 +55,46 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
static bool firstCall = true;
if (firstCall) {
NodeType::init();
// register the SharedNodePointer meta-type for signals/slots
qRegisterMetaType<SharedNodePointer>();
firstCall = false;
}
_nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort);
qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort();
if (dtlsListenPort > 0) {
// only create the DTLS socket during constructor if a custom port is passed
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort);
qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
const int LARGER_BUFFER_SIZE = 1048576;
changeSocketBufferSizes(LARGER_BUFFER_SIZE);
// check for local socket updates every so often
const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000;
QTimer* localSocketUpdate = new QTimer(this);
connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSockAddr);
localSocketUpdate->start(LOCAL_SOCKET_UPDATE_INTERVAL_MSECS);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, &QTimer::timeout, this, &LimitedNodeList::removeSilentNodes);
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);
// check the local socket right now
updateLocalSockAddr();
_packetStatTimer.start();
}
void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) {
QUuid oldUUID = _sessionUUID;
_sessionUUID = sessionUUID;
if (sessionUUID != oldUUID) {
qCDebug(networking) << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID)
<< "to" << uuidStringWithoutCurlyBraces(_sessionUUID);
@ -120,16 +120,16 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() {
if (!_dtlsSocket) {
// DTLS socket getter called but no DTLS socket exists, create it now
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress);
// we're using DTLS and our socket is good to go, so make the required DTLS changes
// DTLS requires that IP_DONTFRAG be set
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
qCDebug(networking) << "LimitedNodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
return *_dtlsSocket;
}
@ -140,7 +140,7 @@ void LimitedNodeList::changeSocketBufferSizes(int numBytes) {
if (i == 0) {
bufferOpt = QAbstractSocket::SendBufferSizeSocketOption;
bufferTypeString = "send";
} else {
bufferOpt = QAbstractSocket::ReceiveBufferSizeSocketOption;
bufferTypeString = "receive";
@ -148,7 +148,7 @@ void LimitedNodeList::changeSocketBufferSizes(int numBytes) {
int oldBufferSize = _nodeSocket.socketOption(bufferOpt).toInt();
if (oldBufferSize < numBytes) {
int newBufferSize = _nodeSocket.socketOption(bufferOpt).toInt();
qCDebug(networking) << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to"
<< newBufferSize << "bytes";
} else {
@ -162,13 +162,13 @@ void LimitedNodeList::changeSocketBufferSizes(int numBytes) {
bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
PacketType checkType = packetTypeForPacket(packet);
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
if (packet[numPacketTypeBytes] != versionForPacketType(checkType)
&& checkType != PacketTypeStunResponse) {
PacketType mismatchType = packetTypeForPacket(packet);
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
QUuid senderUUID = uuidFromPacketHeader(packet);
if (!versionDebugSuppressMap.contains(senderUUID, checkType)) {
qCDebug(networking) << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender"
@ -176,13 +176,13 @@ bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
<< qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected.";
emit packetVersionMismatch();
versionDebugSuppressMap.insert(senderUUID, checkType);
}
return false;
}
if (!NON_VERIFIED_PACKETS.contains(checkType)) {
// figure out which node this is from
SharedNodePointer sendingNode = sendingNodeForPacket(packet);
@ -192,26 +192,26 @@ bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
return true;
} else {
static QMultiMap<QUuid, PacketType> hashDebugSuppressMap;
QUuid senderUUID = uuidFromPacketHeader(packet);
if (!hashDebugSuppressMap.contains(senderUUID, checkType)) {
qCDebug(networking) << "Packet hash mismatch on" << checkType << "- Sender"
<< uuidFromPacketHeader(packet);
hashDebugSuppressMap.insert(senderUUID, checkType);
}
}
} else {
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex("Packet of type \\d+ received from unknown node with UUID");
qCDebug(networking) << "Packet of type" << checkType << "received from unknown node with UUID"
<< qPrintable(uuidStringWithoutCurlyBraces(uuidFromPacketHeader(packet)));
}
} else {
return true;
}
return false;
}
@ -226,7 +226,7 @@ qint64 LimitedNodeList::readDatagram(QByteArray& incomingPacket, QHostAddress* a
} else {
emit dataReceived(NodeType::Unassigned, incomingPacket.size());
}
return result;
}
@ -235,14 +235,14 @@ qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const HifiSock
// stat collection for packets
++_numCollectedPackets;
_numCollectedBytes += datagram.size();
qint64 bytesWritten = _nodeSocket.writeDatagram(datagram,
destinationSockAddr.getAddress(), destinationSockAddr.getPort());
if (bytesWritten < 0) {
qCDebug(networking) << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString();
}
return bytesWritten;
}
@ -269,7 +269,7 @@ qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram,
}
QByteArray datagramCopy = datagram;
// if we're here and the connection secret is null, debug out - this could be a problem
if (destinationNode->getConnectionSecret().isNull()) {
qDebug() << "LimitedNodeList::writeDatagram called for verified datagram with null connection secret for"
@ -278,21 +278,21 @@ qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram,
}
// perform replacement of hash and optionally also sequence number in the header
if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) {
if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) {
PacketSequenceNumber sequenceNumber = getNextSequenceNumberForPacket(destinationNode->getUUID(), packetType);
replaceHashAndSequenceNumberInPacket(datagramCopy, destinationNode->getConnectionSecret(),
sequenceNumber, packetType);
} else {
replaceHashInPacket(datagramCopy, destinationNode->getConnectionSecret(), packetType);
}
emit dataSent(destinationNode->getType(), datagram.size());
auto bytesWritten = writeDatagram(datagramCopy, *destinationSockAddr);
// Keep track of per-destination-node bandwidth
destinationNode->recordBytesSent(bytesWritten);
return bytesWritten;
}
// didn't have a destinationNode to send to, return 0
return 0;
}
@ -311,7 +311,7 @@ qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, cons
return 0;
}
}
PacketType packetType = packetTypeForPacket(datagram);
// optionally peform sequence number replacement in the header
@ -328,7 +328,7 @@ qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, cons
return writeDatagram(datagram, *destinationSockAddr);
}
}
// didn't have a destinationNode to send to, return 0
return 0;
}
@ -348,11 +348,11 @@ qint64 LimitedNodeList::writeUnverifiedDatagram(const char* data, qint64 size, c
}
PacketSequenceNumber LimitedNodeList::getNextSequenceNumberForPacket(const QUuid& nodeUUID, PacketType packetType) {
// Thanks to std::map and std::unordered_map this line either default constructs the
// Thanks to std::map and std::unordered_map this line either default constructs the
// PacketTypeSequenceMap and the PacketSequenceNumber or returns the existing value.
// We use the postfix increment so that the stored value is incremented and the next
// We use the postfix increment so that the stored value is incremented and the next
// return gives the correct value.
return _packetSequenceNumbers[nodeUUID][packetType]++;
}
@ -367,21 +367,21 @@ void LimitedNodeList::processNodeData(const HifiSockAddr& senderSockAddr, const
int LimitedNodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) {
QMutexLocker locker(&matchingNode->getMutex());
matchingNode->setLastHeardMicrostamp(usecTimestampNow());
// if this was a sequence numbered packet we should store the last seq number for
// a packet of this type for this node
PacketType packetType = packetTypeForPacket(packet);
if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) {
matchingNode->setLastSequenceNumberForPacketType(sequenceNumberFromHeader(packet, packetType), packetType);
}
NodeData* linkedData = matchingNode->getLinkedData();
if (!linkedData && linkedDataCreateCallback) {
linkedDataCreateCallback(matchingNode.data());
}
if (linkedData) {
QMutexLocker linkedDataLocker(&linkedData->getMutex());
return linkedData->parseData(packet);
@ -391,42 +391,42 @@ int LimitedNodeList::updateNodeWithDataFromPacket(const SharedNodePointer& match
int LimitedNodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) {
SharedNodePointer matchingNode = sendingNodeForPacket(packet);
if (matchingNode) {
return updateNodeWithDataFromPacket(matchingNode, packet);
}
// we weren't able to match the sender address to the address we have for this node, unlock and don't parse
return 0;
}
SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) {
QReadLocker readLocker(&_nodeMutex);
NodeHash::const_iterator it = _nodeHash.find(nodeUUID);
return it == _nodeHash.cend() ? SharedNodePointer() : it->second;
}
SharedNodePointer LimitedNodeList::sendingNodeForPacket(const QByteArray& packet) {
QUuid nodeUUID = uuidFromPacketHeader(packet);
// return the matching node, or NULL if there is no match
return nodeWithUUID(nodeUUID);
}
void LimitedNodeList::eraseAllNodes() {
qCDebug(networking) << "Clearing the NodeList. Deleting all nodes in list.";
QSet<SharedNodePointer> killedNodes;
eachNode([&killedNodes](const SharedNodePointer& node){
killedNodes.insert(node);
});
// iterate the current nodes, emit that they are dying and remove them from the hash
_nodeMutex.lockForWrite();
_nodeHash.clear();
_nodeMutex.unlock();
foreach(const SharedNodePointer& killedNode, killedNodes) {
handleNodeKill(killedNode);
}
@ -438,17 +438,17 @@ void LimitedNodeList::reset() {
void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
_nodeMutex.lockForRead();
NodeHash::iterator it = _nodeHash.find(nodeUUID);
if (it != _nodeHash.end()) {
SharedNodePointer matchingNode = it->second;
_nodeMutex.unlock();
_nodeMutex.lockForWrite();
_nodeHash.unsafe_erase(it);
_nodeMutex.unlock();
handleNodeKill(matchingNode);
} else {
_nodeMutex.unlock();
@ -472,34 +472,34 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool canAdjustLocks, bool canRez) {
NodeHash::const_iterator it = _nodeHash.find(uuid);
if (it != _nodeHash.end()) {
SharedNodePointer& matchingNode = it->second;
matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket);
matchingNode->setCanAdjustLocks(canAdjustLocks);
matchingNode->setCanRez(canRez);
return matchingNode;
} else {
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez);
SharedNodePointer newNodePointer(newNode);
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));
qCDebug(networking) << "Added" << *newNode;
emit nodeAdded(newNodePointer);
return newNodePointer;
}
}
unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
unsigned n = 0;
eachNode([&](const SharedNodePointer& node){
if (destinationNodeTypes.contains(node->getType())) {
writeDatagram(packet, node);
@ -516,35 +516,35 @@ QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVeri
QByteArray pingPacket = byteArrayWithUUIDPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing,
packetUUID);
QDataStream packetStream(&pingPacket, QIODevice::Append);
packetStream << pingType;
packetStream << usecTimestampNow();
return pingPacket;
}
QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID) {
QDataStream pingPacketStream(pingPacket);
pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket));
PingType_t typeFromOriginalPing;
pingPacketStream >> typeFromOriginalPing;
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
PacketType replyType = (packetTypeForPacket(pingPacket) == PacketTypePing)
? PacketTypePingReply : PacketTypeUnverifiedPingReply;
QUuid packetUUID = packetHeaderID.isNull() ? _sessionUUID : packetHeaderID;
QByteArray replyPacket = byteArrayWithUUIDPopulatedHeader(replyType, packetUUID);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();
return replyPacket;
}
@ -566,9 +566,9 @@ void LimitedNodeList::resetPacketStats() {
}
void LimitedNodeList::removeSilentNodes() {
QSet<SharedNodePointer> killedNodes;
eachNodeHashIterator([&](NodeHash::iterator& it){
SharedNodePointer node = it->second;
node->getMutex().lock();
@ -582,10 +582,10 @@ void LimitedNodeList::removeSilentNodes() {
// we didn't erase this node, push the iterator forwards
++it;
}
node->getMutex().unlock();
});
foreach(const SharedNodePointer& killedNode, killedNodes) {
handleNodeKill(killedNode);
}
@ -595,38 +595,38 @@ const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
const int NUM_BYTES_STUN_HEADER = 20;
void LimitedNodeList::sendSTUNRequest() {
unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER];
int packetIndex = 0;
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
// leading zeros + message type
const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001);
memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE));
packetIndex += sizeof(REQUEST_MESSAGE_TYPE);
// message length (no additional attributes are included)
uint16_t messageLength = 0;
memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength));
packetIndex += sizeof(messageLength);
memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER));
packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
// transaction ID (random 12-byte unsigned integer)
const uint NUM_TRANSACTION_ID_BYTES = 12;
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
_nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket),
_stunSockAddr.getAddress(), _stunSockAddr.getPort());
}
void LimitedNodeList::rebindNodeSocket() {
quint16 oldPort = _nodeSocket.localPort();
_nodeSocket.close();
_nodeSocket.bind(QHostAddress::AnyIPv4, oldPort);
}
@ -636,125 +636,125 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) {
// and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS
const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4;
const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020);
const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE);
int attributeStartIndex = NUM_BYTES_STUN_HEADER;
if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) {
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
while (attributeStartIndex < packet.size()) {
if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
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;
memcpy(&addressFamily, packet.data() + byteIndex, sizeof(addressFamily));
byteIndex += sizeof(addressFamily);
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
// grab the X-Port
uint16_t xorMappedPort = 0;
memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort));
uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
byteIndex += sizeof(xorMappedPort);
// grab the X-Address
uint32_t xorMappedAddress = 0;
memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress));
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
QHostAddress newPublicAddress = QHostAddress(stunAddress);
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
_publicSockAddr.getPort());
emit publicSockAddrChanged(_publicSockAddr);
emit publicSockAddrChanged(_publicSockAddr);
}
return true;
}
} else {
// push forward attributeStartIndex by the length of this attribute
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
uint16_t attributeLength = 0;
memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
sizeof(attributeLength));
attributeLength = ntohs(attributeLength);
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
}
}
}
return false;
}
void LimitedNodeList::updateLocalSockAddr() {
HifiSockAddr newSockAddr(getLocalAddress(), _nodeSocket.localPort());
if (newSockAddr != _localSockAddr) {
if (_localSockAddr.isNull()) {
qCDebug(networking) << "Local socket is" << newSockAddr;
} else {
qCDebug(networking) << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
}
_localSockAddr = newSockAddr;
emit localSockAddrChanged(_localSockAddr);
}
}
void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID, const QUuid& connectionRequestID) {
if (headerID.isNull()) {
headerID = _sessionUUID;
}
QByteArray iceRequestByteArray = byteArrayWithUUIDPopulatedHeader(PacketTypeIceServerHeartbeat, headerID);
QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append);
iceDataStream << _publicSockAddr << _localSockAddr;
if (!connectionRequestID.isNull()) {
iceDataStream << connectionRequestID;
qCDebug(networking) << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(connectionRequestID);
}
writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr);
}
void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort) {
// save our local port to shared memory so that assignment client children know how to talk to this parent
QSharedMemory* sharedPortMem = new QSharedMemory(key, parent);
// attempt to create the shared memory segment
if (sharedPortMem->create(sizeof(localPort)) || sharedPortMem->attach()) {
sharedPortMem->lock();
memcpy(sharedPortMem->data(), &localPort, sizeof(localPort));
sharedPortMem->unlock();
qCDebug(networking) << "Wrote local listening port" << localPort << "to shared memory at key" << key;
} else {
qWarning() << "Failed to create and attach to shared memory to share local port with assignment-client children.";
@ -762,22 +762,15 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p
}
bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem,
quint16& localPort) {
if (!sharedMem) {
sharedMem = new QSharedMemory(key, this);
if (!sharedMem->attach(QSharedMemory::ReadOnly)) {
qWarning() << "Could not attach to shared memory at key" << key;
}
}
if (sharedMem->isAttached()) {
sharedMem->lock();
memcpy(&localPort, sharedMem->data(), sizeof(localPort));
sharedMem->unlock();
bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {
QSharedMemory sharedMem(key);
if (!sharedMem.attach(QSharedMemory::ReadOnly)) {
qWarning() << "Could not attach to shared memory at key" << key;
return false;
} else {
sharedMem.lock();
memcpy(&localPort, sharedMem.data(), sizeof(localPort));
sharedMem.unlock();
return true;
}
return false;
}

View file

@ -79,7 +79,7 @@ namespace PingType {
class LimitedNodeList : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
const QUuid& getSessionUUID() const { return _sessionUUID; }
void setSessionUUID(const QUuid& sessionUUID);
@ -89,22 +89,22 @@ public:
bool getThisNodeCanRez() const { return _thisNodeCanRez; }
void setThisNodeCanRez(bool canRez);
void rebindNodeSocket();
QUdpSocket& getNodeSocket() { return _nodeSocket; }
QUdpSocket& getDTLSSocket();
bool packetVersionAndHashMatch(const QByteArray& packet);
QByteArray byteArrayWithPopulatedHeader(PacketType packetType)
QByteArray byteArrayWithPopulatedHeader(PacketType packetType)
{ return byteArrayWithUUIDPopulatedHeader(packetType, _sessionUUID); }
int populatePacketHeader(QByteArray& packet, PacketType packetType)
int populatePacketHeader(QByteArray& packet, PacketType packetType)
{ return populatePacketHeaderWithUUID(packet, packetType, _sessionUUID); }
int populatePacketHeader(char* packet, PacketType packetType)
int populatePacketHeader(char* packet, PacketType packetType)
{ return populatePacketHeaderWithUUID(packet, packetType, _sessionUUID); }
qint64 readDatagram(QByteArray& incomingPacket, QHostAddress* address, quint16 * port);
qint64 writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode,
const HifiSockAddr& overridenSockAddr = HifiSockAddr());
@ -120,16 +120,16 @@ public:
const HifiSockAddr& overridenSockAddr = HifiSockAddr());
void (*linkedDataCreateCallback)(Node *);
int size() const { return _nodeHash.size(); }
SharedNodePointer nodeWithUUID(const QUuid& nodeUUID);
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool canAdjustLocks, bool canRez);
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
@ -144,21 +144,21 @@ public:
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void resetPacketStats();
QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true,
const QUuid& packetHeaderID = QUuid());
QByteArray constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID = QUuid());
virtual void sendSTUNRequest();
virtual bool processSTUNResponse(const QByteArray& packet);
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid());
template<typename NodeLambda>
void eachNode(NodeLambda functor) {
QReadLocker readLock(&_nodeMutex);
for (NodeHash::const_iterator it = _nodeHash.cbegin(); it != _nodeHash.cend(); ++it) {
functor(it->second);
}
@ -178,44 +178,44 @@ public:
template<typename BreakableNodeLambda>
void eachNodeBreakable(BreakableNodeLambda functor) {
QReadLocker readLock(&_nodeMutex);
for (NodeHash::const_iterator it = _nodeHash.cbegin(); it != _nodeHash.cend(); ++it) {
if (!functor(it->second)) {
break;
}
}
}
template<typename PredLambda>
SharedNodePointer nodeMatchingPredicate(const PredLambda predicate) {
QReadLocker readLock(&_nodeMutex);
for (NodeHash::const_iterator it = _nodeHash.cbegin(); it != _nodeHash.cend(); ++it) {
if (predicate(it->second)) {
return it->second;
}
}
return SharedNodePointer();
}
void putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort);
bool getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem, quint16& localPort);
bool getLocalServerPortFromSharedMemory(const QString key, quint16& localPort);
public slots:
void reset();
void eraseAllNodes();
void removeSilentNodes();
void updateLocalSockAddr();
void killNodeWithUUID(const QUuid& nodeUUID);
signals:
void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID);
void nodeAdded(SharedNodePointer);
void nodeKilled(SharedNodePointer);
void localSockAddrChanged(const HifiSockAddr& localSockAddr);
void publicSockAddrChanged(const HifiSockAddr& publicSockAddr);
@ -226,18 +226,18 @@ signals:
void dataReceived(const quint8 channel_type, const int bytes);
void packetVersionMismatch();
protected:
LimitedNodeList(unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton
void operator=(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton
qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr);
PacketSequenceNumber getNextSequenceNumberForPacket(const QUuid& nodeUUID, PacketType packetType);
void changeSocketBufferSizes(int numBytes);
void handleNodeKill(const SharedNodePointer& node);
QUuid _sessionUUID;
@ -258,17 +258,17 @@ protected:
bool _thisNodeCanRez;
std::unordered_map<QUuid, PacketTypeSequenceMap, UUIDHasher> _packetSequenceNumbers;
template<typename IteratorLambda>
void eachNodeHashIterator(IteratorLambda functor) {
QWriteLocker writeLock(&_nodeMutex);
NodeHash::iterator it = _nodeHash.begin();
while (it != _nodeHash.end()) {
functor(it);
}
}
};
#endif // hifi_LimitedNodeList_h

View file

@ -335,12 +335,8 @@ void NodeList::sendDomainServerCheckIn() {
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|| _domainHandler.getHostname() == "localhost") {
static QSharedMemory* localDSPortSharedMem = NULL;
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
localDSPortSharedMem,
domainPort);
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort;
_domainHandler.setPort(domainPort);
}

View file

@ -159,16 +159,17 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
}
}
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing) {
int dotIndex = keyPath.indexOf('.');
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
if (variantMap.contains(firstKey)) {
if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
if (dotIndex == -1) {
return &variantMap[firstKey];
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1));
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
shouldCreateIfMissing);
}
}

View file

@ -37,6 +37,6 @@ private:
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
#endif // hifi_HifiConfigVariantMap_h