diff --git a/BUILD.md b/BUILD.md index 6464f14d08..487bcb0eb1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,10 +1,12 @@ Dependencies === -* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.11 +* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.12.2 * [Qt](http://qt-project.org/downloads) ~> 5.2.0 * [zLib](http://www.zlib.net/) ~> 1.2.8 * [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.2 * [qxmpp](https://code.google.com/p/qxmpp/) ~> 0.7.6 +* [GnuTLS](http://gnutls.org/download.html) ~> 3.2.12 + * IMPORTANT: GnuTLS 3.2.12 is critical to avoid a security vulnerability. #####Linux only * [freeglut](http://freeglut.sourceforge.net/) ~> 2.8.0 @@ -50,12 +52,18 @@ Should you choose not to install Qt5 via a package manager that handles dependen libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev +#####GnuTLS + +If `libgnutls28-dev` 3.2.12 or higher is available via your package manager, it would be easiest to grab it from there. At the time of this writing that is not the case for any version of Ubuntu, so it will need to be built from source. + +`gmplib` is a dependency for GnuTLS. On Ubuntu, we were unable to build `hogweed` (part of `libnettle`) with `gmpib` 6.x.x. If nettle is not built with `hogweed`, GnuTLS will fail to build. If you run into this problem, try version 4.2.1 of `gmplib`. + ####OS X #####Package Managers [Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple. brew tap highfidelity/homebrew-formulas - brew install cmake glm zlib + brew install cmake glm zlib gnutls brew install highfidelity/formulas/qt5 brew link qt5 --force brew install highfidelity/formulas/qxmpp @@ -71,7 +79,7 @@ If Xcode is your editor of choice, you can ask CMake to generate Xcode project f After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run. -If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug directories`. +If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories. Windows === @@ -100,11 +108,12 @@ Once Qt is installed, you need to manually configure the following: NOTE: zLib should configure itself correctly on install. However, sometimes zLib doesn't properly detect system components and fails to configure itself correctly. When it fails, it will not correctly set the #if HAVE_UNISTD_H at line 287 of zconf.h to #if 0... if it fails, you're build will have errors in the voxels target. You can correct this by setting the #if to 0 instead of 1, since Windows does not have unistd.h. ####External Libraries -We don't currently have a Windows installer, so before running Interface, you will need to ensure that all required resources are loadable. -CMake will need to know where the headers and libraries for required external dependencies are. If you installed ZLIB using the installer, the FindZLIB cmake module will be able to find it. This isn't the case for the others. +CMake will need to know where the headers and libraries for required external dependencies are. -The recommended route for CMake to find external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure: +If you installed zLib using the installer, the Cmake find module for zLib should locate it on your system. + +The recommended route for CMake to find the other external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure: root_lib_dir -> glm @@ -121,15 +130,30 @@ The recommended route for CMake to find external dependencies is to place all of -> qxmpp -> include -> lib - -*NOTE: Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.* + -> gnutls + -> bin + -> include + -> lib For many of the external libraries where precompiled binaries are readily available you should be able to simply copy the extracted folder that you get from the download links provided at the top of the guide. Otherwise you may need to build from source and install the built product to this directory. The `root_lib_dir` in the above example can be wherever you choose on your system - as long as the environment variable HIFI_LIB_DIR is set to it. +*NOTE: Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.* + *NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules* -#### DLLs -As with the Qt libraries, you will need to make sure the directory containing dynamically-linked libraries is in your path. For example, for a dynamically linked build of freeglut, the directory to add to your path in which the DLL is found is `FREEGLUT_DIR/bin`. Where possible, you can use static builds of the external dependencies to avoid this requirement. +##### DLLs +As with the Qt libraries, you will need to make sure the directories containing dynamically-linked libraries is in your path. + +For example, for a dynamically linked build of freeglut, the directory to add to your path in which the DLL is found is `FREEGLUT_DIR/bin`. Where possible, you can use static builds of the external dependencies to avoid this requirement. + +#####GnuTLS +You can get a precompiled version of GnuTLS for Windows [here](http://gnutls.org/download.html). + +To use GnuTLS with Visual Studio, you will need to create `libgnutls-28.lib`, the import library for Visual Studio projects. this is done using the `lib` command in the `bin` folder of your GnuTLS download. Run the following in a Visual Studio Command Prompt (found in the tools menu of Visual Studio). + + $GNUTLS_DIR\bin> lib /def:libgnutls-28.def + +This will create `libgnutls-28.lib` in the `bin` folder. Copy that file to the `lib` sub-folder of your GnuTLS folder, and the Cmake FindGnuTLS module in this repo will find it during the Cmake run. ####Building in Visual Studio Follow the same build steps from the CMake section, but pass a different generator to CMake. diff --git a/animation-server/CMakeLists.txt b/animation-server/CMakeLists.txt index 7d3222327a..5d9f3604bc 100644 --- a/animation-server/CMakeLists.txt +++ b/animation-server/CMakeLists.txt @@ -29,4 +29,18 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +# link the hifi networking library +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +find_package(GnuTLS REQUIRED) + +# include the GnuTLS dir +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +# link GnuTLS +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp index 6a4d1ea76d..ffb98b4ae0 100644 --- a/animation-server/src/AnimationServer.cpp +++ b/animation-server/src/AnimationServer.cpp @@ -726,12 +726,12 @@ AnimationServer::AnimationServer(int &argc, char **argv) : ::wantLocalDomain = cmdOptionExists(argc, (const char**) argv,local); if (::wantLocalDomain) { printf("Local Domain MODE!\n"); - nodeList->getDomainInfo().setIPToLocalhost(); + nodeList->getDomainHandler().setIPToLocalhost(); } const char* domainHostname = getCmdOption(argc, (const char**) argv, "--domain"); if (domainHostname) { - NodeList::getInstance()->getDomainInfo().setHostname(domainHostname); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostname); } const char* packetsPerSecondCommand = getCmdOption(argc, (const char**) argv, "--pps"); @@ -800,11 +800,11 @@ AnimationServer::AnimationServer(int &argc, char **argv) : QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readPendingDatagrams())); } diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 505708a899..a8343e94ad 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -30,9 +30,19 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}") +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + if (UNIX) target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS}) endif (UNIX) @@ -41,4 +51,4 @@ IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f376c4e981..f48c4b9401 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -149,7 +149,7 @@ void Agent::run() { // figure out the URL for the script for this agent assignment QString scriptURLString("http://%1:8080/assignment/%2"); - scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainInfo().getIP().toString(), + scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), uuidStringWithoutCurlyBraces(_uuid)); QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 9d9bfa3113..f0d23cac62 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include #include #include @@ -32,64 +34,58 @@ int hifiSockAddrMeta = qRegisterMetaType("HifiSockAddr"); AssignmentClient::AssignmentClient(int &argc, char **argv) : QCoreApplication(argc, argv), - _currentAssignment() + _currentAssignment(), + _assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME) { + DTLSClientSession::globalInit(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); QSettings::setDefaultFormat(QSettings::IniFormat); + QStringList argumentList = arguments(); + // register meta type is required for queued invoke method on Assignment subclasses // set the logging target to the the CHILD_TARGET_NAME Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); - const char ASSIGNMENT_TYPE_OVVERIDE_OPTION[] = "-t"; - const char* assignmentTypeString = getCmdOption(argc, (const char**)argv, ASSIGNMENT_TYPE_OVVERIDE_OPTION); + const QString ASSIGNMENT_TYPE_OVVERIDE_OPTION = "-t"; + int argumentIndex = argumentList.indexOf(ASSIGNMENT_TYPE_OVVERIDE_OPTION); Assignment::Type requestAssignmentType = Assignment::AllTypes; - if (assignmentTypeString) { - // the user is asking to only be assigned to a particular type of assignment - // so set that as the ::overridenAssignmentType to be used in requests - requestAssignmentType = (Assignment::Type) atoi(assignmentTypeString); + if (argumentIndex != -1) { + requestAssignmentType = (Assignment::Type) argumentList[argumentIndex + 1].toInt(); } - const char ASSIGNMENT_POOL_OPTION[] = "--pool"; - const char* requestAssignmentPool = getCmdOption(argc, (const char**) argv, ASSIGNMENT_POOL_OPTION); + const QString ASSIGNMENT_POOL_OPTION = "--pool"; + argumentIndex = argumentList.indexOf(ASSIGNMENT_POOL_OPTION); + QString assignmentPool; + + if (argumentIndex != -1) { + assignmentPool = argumentList[argumentIndex + 1]; + } // setup our _requestAssignment member variable from the passed arguments - _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool); + _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); // create a NodeList as an unassigned client NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); - const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a"; - const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p"; + // check for an overriden assignment server hostname + const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "-a"; - // grab the overriden assignment-server hostname from argv, if it exists - const char* customAssignmentServerHostname = getCmdOption(argc, (const char**)argv, CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - const char* customAssignmentServerPortString = getCmdOption(argc,(const char**)argv, CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION); + argumentIndex = argumentList.indexOf(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - HifiSockAddr customAssignmentSocket; - - if (customAssignmentServerHostname || customAssignmentServerPortString) { + if (argumentIndex != -1) { + _assignmentServerHostname = argumentList[argumentIndex + 1]; - // set the custom port or default if it wasn't passed - unsigned short assignmentServerPort = customAssignmentServerPortString - ? atoi(customAssignmentServerPortString) : DEFAULT_DOMAIN_SERVER_PORT; + // set the custom assignment socket on our NodeList + HifiSockAddr customAssignmentSocket = HifiSockAddr(_assignmentServerHostname, DEFAULT_DOMAIN_SERVER_PORT); - // set the custom hostname or default if it wasn't passed - if (!customAssignmentServerHostname) { - customAssignmentServerHostname = DEFAULT_ASSIGNMENT_SERVER_HOSTNAME; - } - - customAssignmentSocket = HifiSockAddr(customAssignmentServerHostname, assignmentServerPort); - } - - // set the custom assignment socket if we have it - if (!customAssignmentSocket.isNull()) { nodeList->setAssignmentServerSocket(customAssignmentSocket); } @@ -108,6 +104,10 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : this, &AssignmentClient::handleAuthenticationRequest); } +AssignmentClient::~AssignmentClient() { + DTLSClientSession::globalDeinit(); +} + void AssignmentClient::sendAssignmentRequest() { if (!_currentAssignment) { NodeList::getInstance()->sendAssignment(_requestAssignment); @@ -133,12 +133,12 @@ void AssignmentClient::readPendingDatagrams() { if (_currentAssignment) { qDebug() << "Received an assignment -" << *_currentAssignment; - // switch our nodelist domain IP and port to whoever sent us the assignment + // switch our DomainHandler hostname and port to whoever sent us the assignment - nodeList->getDomainInfo().setSockAddr(senderSockAddr); - nodeList->getDomainInfo().setAssignmentUUID(_currentAssignment->getUUID()); + nodeList->getDomainHandler().setSockAddr(senderSockAddr, _assignmentServerHostname); + nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID()); - qDebug() << "Destination IP for assignment is" << nodeList->getDomainInfo().getIP().toString(); + qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); // start the deployed assignment AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this); diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 1ad8cca244..89fe74f044 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -20,6 +20,7 @@ class AssignmentClient : public QCoreApplication { Q_OBJECT public: AssignmentClient(int &argc, char **argv); + ~AssignmentClient(); private slots: void sendAssignmentRequest(); void readPendingDatagrams(); @@ -28,6 +29,7 @@ private slots: private: Assignment _requestAssignment; SharedAssignmentPointer _currentAssignment; + QString _assignmentServerHostname; }; #endif // hifi_AssignmentClient_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 35b522ff2f..407a64d7a8 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -21,7 +21,6 @@ #include #ifdef _WIN32 -#include "Syssocket.h" #include "Systime.h" #include #else @@ -372,7 +371,7 @@ void AudioMixer::sendStatsPacket() { statsObject["average_mixes_per_listener"] = 0.0; } - ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); +// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; _sumMixes = 0; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 9289d640f5..f35285124f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "AvatarMixerClientData.h" AvatarMixerClientData::AvatarMixerClientData() : diff --git a/cmake/modules/FindGnuTLS.cmake b/cmake/modules/FindGnuTLS.cmake new file mode 100644 index 0000000000..0e1899864b --- /dev/null +++ b/cmake/modules/FindGnuTLS.cmake @@ -0,0 +1,41 @@ +# +# FindGnuTLS.cmake +# +# Try to find the GnuTLS library +# +# You can provide a GNUTLS_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# GNUTLS_FOUND - system found GnuTLS +# GNUTLS_INCLUDE_DIR - the GnuTLS include directory +# GNUTLS_LIBRARY - Link this to use GnuTLS +# +# Created on 3/31/2014 by Stephen Birarda +# Copyright (c) 2014 High Fidelity +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +if (GNUTLS_LIBRARY AND GNUTLS_INCLUDE_DIRS) + # in cache already + set(GNUTLS_FOUND TRUE) +else () + set(GNUTLS_SEARCH_DIRS "${GNUTLS_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/gnutls") + + find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h PATH_SUFFIXES include HINTS ${GNUTLS_SEARCH_DIRS}) + + find_library(GNUTLS_LIBRARY NAMES gnutls libgnutls libgnutls-28 PATH_SUFFIXES lib HINTS ${GNUTLS_SEARCH_DIRS}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GNUTLS DEFAULT_MSG GNUTLS_INCLUDE_DIR GNUTLS_LIBRARY) + + if (WIN32 AND NOT GNUTLS_FOUND) + message(STATUS "If you're generating a MSVC environment, you'll need to run the command") + message(STATUS "$GnuTLS-DIR\\bin>lib /def:libgnutls-28.def") + message(STATUS "From the MSVC command prompt to generate the .lib file and copy it into") + message(STATUS "the GnuTLS lib folder. Replace $GnuTLS-DIR in the command with the directory") + message(STATUS "containing GnuTLS.") + endif () +endif () diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake index d5f23143f6..420186d969 100644 --- a/cmake/modules/FindQxmpp.cmake +++ b/cmake/modules/FindQxmpp.cmake @@ -9,7 +9,7 @@ # # QXMPP_FOUND - system found qxmpp # QXMPP_INCLUDE_DIRS - the qxmpp include directory -# QXMPP_LIBRARIES - Link this to use qxmpp +# QXMPP_LIBRARY - Link this to use qxmpp # # Created on 3/10/2014 by Stephen Birarda # Copyright 2014 High Fidelity, Inc. @@ -31,14 +31,4 @@ else () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIR QXMPP_LIBRARY) - - if (QXMPP_FOUND) - if (NOT QXMPP_FIND_QUIETLY) - message(STATUS "Found qxmpp: ${QXMPP_LIBRARY}") - endif () - else () - if (QXMPP_FIND_REQUIRED) - message(FATAL_ERROR "Could not find qxmpp") - endif () - endif () endif () \ No newline at end of file diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 16bdb0df6b..4736c2438b 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -17,6 +17,7 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") find_package(Qt5Network REQUIRED) +find_package(GnuTLS REQUIRED) include(${MACRO_DIR}/SetupHifiProject.cmake) @@ -33,11 +34,21 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD # link the shared hifi library include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}") +# include the GnuTLS dir +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network) \ No newline at end of file +# link QtNetwork and GnuTLS +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/domain-server/src/DTLSServerSession.cpp b/domain-server/src/DTLSServerSession.cpp new file mode 100644 index 0000000000..a80c54ee02 --- /dev/null +++ b/domain-server/src/DTLSServerSession.cpp @@ -0,0 +1,18 @@ +// +// DTLSServerSession.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DTLSServerSession.h" + +DTLSServerSession::DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DTLSSession(GNUTLS_SERVER, dtlsSocket, destinationSocket) +{ + +} \ No newline at end of file diff --git a/domain-server/src/DTLSServerSession.h b/domain-server/src/DTLSServerSession.h new file mode 100644 index 0000000000..5fdc602df7 --- /dev/null +++ b/domain-server/src/DTLSServerSession.h @@ -0,0 +1,24 @@ +// +// DTLSServerSession.h +// domain-server/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSServerSession_h +#define hifi_DTLSServerSession_h + +#include + +#include + +class DTLSServerSession : public DTLSSession { +public: + DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); +}; + +#endif // hifi_DTLSServerSession_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 05d1fcd534..1bda2aa75c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - #include #include #include @@ -19,13 +17,17 @@ #include #include +#include + #include +#include #include #include #include #include #include "DomainServerNodeData.h" +#include "DummyDTLSSession.h" #include "DomainServer.h" @@ -36,126 +38,180 @@ DomainServer::DomainServer(int argc, char* argv[]) : _HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _staticAssignmentHash(), _assignmentQueue(), - _nodeAuthenticationURL(), - _redeemedTokenResponses() + _isUsingDTLS(false), + _x509Credentials(NULL), + _dhParams(NULL), + _priorityCache(NULL), + _dtlsSessions() { + gnutls_global_init(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); QSettings::setDefaultFormat(QSettings::IniFormat); - _argumentList = arguments(); - int argumentIndex = 0; + _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - // check if this domain server should use no authentication or a custom hostname for authentication - const QString DEFAULT_AUTH_OPTION = "--defaultAuth"; - const QString CUSTOM_AUTH_OPTION = "--customAuth"; - if ((argumentIndex = _argumentList.indexOf(DEFAULT_AUTH_OPTION) != -1)) { - _nodeAuthenticationURL = QUrl(DEFAULT_NODE_AUTH_URL); - } else if ((argumentIndex = _argumentList.indexOf(CUSTOM_AUTH_OPTION)) != -1) { - _nodeAuthenticationURL = QUrl(_argumentList.value(argumentIndex + 1)); + if (optionallySetupDTLS()) { + // we either read a certificate and private key or were not passed one, good to load assignments + // and set up the node list + qDebug() << "Setting up LimitedNodeList and assignments."; + setupNodeListAndAssignments(); + + if (_isUsingDTLS) { + // we're using DTLS and our NodeList 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 + + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + +#if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER) + qDebug() << "Making required DTLS changes to NodeList DTLS socket."; + + int socketHandle = LimitedNodeList::getInstance()->getDTLSSocket().socketDescriptor(); +#if defined(IP_DONTFRAG) + int optValue = 1;yea + setsockopt(socketHandle, IPPROTO_IP, IP_DONTFRAG, (const void*) optValue, sizeof(optValue)); +#elif defined(IP_MTU_DISCOVER) + int optValue = 1; + setsockopt(socketHandle, IPPROTO_IP, IP_MTU_DISCOVER, (const void*) optValue, sizeof(optValue)); +#endif +#endif + // connect our socket to read datagrams received on the DTLS socket + connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams); + } } +} + +DomainServer::~DomainServer() { + if (_x509Credentials) { + gnutls_certificate_free_credentials(*_x509Credentials); + gnutls_priority_deinit(*_priorityCache); + gnutls_dh_params_deinit(*_dhParams); + + delete _x509Credentials; + delete _priorityCache; + delete _dhParams; + delete _cookieKey; + } + gnutls_global_deinit(); +} + +bool DomainServer::optionallySetupDTLS() { + if (readX509KeyAndCertificate()) { + if (_x509Credentials) { + qDebug() << "Generating Diffie-Hellman parameters."; + + // generate Diffie-Hellman parameters + // When short bit length is used, it might be wise to regenerate parameters often. + int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY); + + _dhParams = new gnutls_dh_params_t; + gnutls_dh_params_init(_dhParams); + gnutls_dh_params_generate2(*_dhParams, dhBits); + + qDebug() << "Successfully generated Diffie-Hellman parameters."; + + // set the D-H paramters on the X509 credentials + gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams); + + // setup the key used for cookie verification + _cookieKey = new gnutls_datum_t; + gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE); + + _priorityCache = new gnutls_priority_t; + const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE"; + gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL); + + _isUsingDTLS = true; + + qDebug() << "Initial DTLS setup complete."; + } - if (!_nodeAuthenticationURL.isEmpty()) { - const QString DATA_SERVER_USERNAME_ENV = "HIFI_DS_USERNAME"; - const QString DATA_SERVER_PASSWORD_ENV = "HIFI_DS_PASSWORD"; + return true; + } else { + return false; + } +} + +bool DomainServer::readX509KeyAndCertificate() { + const QString X509_CERTIFICATE_OPTION = "cert"; + const QString X509_PRIVATE_KEY_OPTION = "key"; + const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; + + QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString(); + + if (!certPath.isEmpty() && !keyPath.isEmpty()) { + // the user wants to use DTLS to encrypt communication with nodes + // let's make sure we can load the key and certificate + _x509Credentials = new gnutls_certificate_credentials_t; + gnutls_certificate_allocate_credentials(_x509Credentials); - // this node will be using an authentication server, let's make sure we have a username/password - QProcessEnvironment sysEnvironment = QProcessEnvironment::systemEnvironment(); + QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); - QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV); - QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV); + qDebug() << "Reading certificate file at" << certPath << "for DTLS."; + qDebug() << "Reading key file at" << keyPath << "for DTLS."; - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_nodeAuthenticationURL); + int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials, + certPath.toLocal8Bit().constData(), + keyPath.toLocal8Bit().constData(), + GNUTLS_X509_FMT_PEM, + keyPassphraseString.toLocal8Bit().constData(), + 0); - if (!username.isEmpty() && !password.isEmpty()) { - - connect(&accountManager, &AccountManager::loginComplete, this, &DomainServer::requestCreationFromDataServer); - - // ask the account manager to log us in from the env variables - accountManager.requestAccessToken(username, password); - } else { - qDebug() << "Authentication was requested against" << qPrintable(_nodeAuthenticationURL.toString()) - << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) - << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Qutting!"; - - // bail out + if (gnutlsReturn < 0) { + qDebug() << "Unable to load certificate or key file." << "Error" << gnutlsReturn << "- domain-server will now quit."; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - - return; + return false; } - } else { - // auth is not requested for domain-server, setup NodeList and assignments now - setupNodeListAndAssignments(); + qDebug() << "Successfully read certificate and private key."; + + } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { + qDebug() << "Missing certificate or private key. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; } -} - -void DomainServer::requestCreationFromDataServer() { - // this slot is fired when we get a valid access token from the data-server - // now let's ask it to set us up with a UUID - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "processCreateResponseFromDataServer"; - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/create", - QNetworkAccessManager::PostOperation, - callbackParams); -} - -void DomainServer::processCreateResponseFromDataServer(const QJsonObject& jsonObject) { - if (jsonObject["status"].toString() == "success") { - // pull out the UUID the data-server is telling us to use, and complete our setup with it - QUuid newSessionUUID = QUuid(jsonObject["data"].toObject()["uuid"].toString()); - setupNodeListAndAssignments(newSessionUUID); - } -} - -void DomainServer::processTokenRedeemResponse(const QJsonObject& jsonObject) { - // pull out the registration token this is associated with - QString registrationToken = jsonObject["data"].toObject()["registration_token"].toString(); - - // if we have a registration token add it to our hash of redeemed token responses - if (!registrationToken.isEmpty()) { - qDebug() << "Redeemed registration token" << registrationToken; - _redeemedTokenResponses.insert(registrationToken, jsonObject); - } + return true; } void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - int argumentIndex = 0; - - const QString CUSTOM_PORT_OPTION = "-p"; + const QString CUSTOM_PORT_OPTION = "port"; unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT; - if ((argumentIndex = _argumentList.indexOf(CUSTOM_PORT_OPTION)) != -1) { - domainServerPort = _argumentList.value(argumentIndex + 1).toUShort(); + if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) { + domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt(); + } + + unsigned short domainServerDTLSPort = 0; + + if (_isUsingDTLS) { + domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; + + const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; + + if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) { + domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); + } } QSet parsedTypes(QSet() << Assignment::AgentType); - parseCommandLineTypeConfigs(_argumentList, parsedTypes); - - const QString CONFIG_FILE_OPTION = "--configFile"; - if ((argumentIndex = _argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) { - QString configFilePath = _argumentList.value(argumentIndex + 1); - readConfigFile(configFilePath, parsedTypes); - } + parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); - NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort); + LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); - // create a random UUID for this session for the domain-server - nodeList->setSessionUUID(sessionUUID); - - connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded); - connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled); + connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); + connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); @@ -163,130 +219,78 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) { +void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence - const QString CONFIG_TYPE_OPTION = "--configType"; - int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION); + const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; + QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - // enumerate all CL config overrides and parse them to files - while (clConfigIndex != -1) { - int clConfigType = argumentList.value(clConfigIndex + 1).toInt(); - if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) { - Assignment::Type assignmentType = (Assignment::Type) clConfigType; - createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType, - argumentList.value(clConfigIndex + 2)); - excludedTypes.insert(assignmentType); - } + // scan for assignment config keys + QStringList variantMapKeys = _argumentVariantMap.keys(); + int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + + while (configIndex != -1) { + // figure out which assignment type this matches + Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); - clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1); - } -} - -// Attempts to read configuration from specified path -// returns true on success, false otherwise -void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) { - if (path.isEmpty()) { - // config file not specified - return; - } - - if (!QFile::exists(path)) { - qWarning("Specified configuration file does not exist!"); - return; - } - - QFile configFile(path); - if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning("Can't open specified configuration file!"); - return; - } else { - qDebug() << "Reading configuration from" << path; - } - - QTextStream configStream(&configFile); - QByteArray configStringByteArray = configStream.readAll().toUtf8(); - QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); - configFile.close(); - - QSet appendedExcludedTypes = excludedTypes; - - foreach (const QString& rootStringValue, configDocObject.keys()) { - int possibleConfigType = rootStringValue.toInt(); - - if (possibleConfigType < Assignment::AllTypes - && !excludedTypes.contains((Assignment::Type) possibleConfigType)) { - // this is an appropriate config type and isn't already in our excluded types - // we are good to parse it - Assignment::Type assignmentType = (Assignment::Type) possibleConfigType; - QString configString = readServerAssignmentConfig(configDocObject, rootStringValue); - createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString); + if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { + QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]]; + + if (mapValue.type() == QVariant::String) { + QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8()); + createStaticAssignmentsForType(assignmentType, deserializedDocument.array()); + } else { + createStaticAssignmentsForType(assignmentType, mapValue.toJsonValue().toArray()); + } excludedTypes.insert(assignmentType); } + + configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1); } } -// find assignment configurations on the specified node name and json object -// returns a string in the form of its equivalent cmd line params -QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) { - QJsonArray nodeArray = jsonObject[nodeName].toArray(); - - QStringList serverConfig; - foreach (const QJsonValue& childValue, nodeArray) { - QString cmdParams; - QJsonObject childObject = childValue.toObject(); - QStringList keys = childObject.keys(); - for (int i = 0; i < keys.size(); i++) { - QString key = keys[i]; - QString value = childObject[key].toString(); - // both cmd line params and json keys are the same - cmdParams += QString("--%1 %2 ").arg(key, value); - } - serverConfig << cmdParams; - } - - // according to split() calls from DomainServer::prepopulateStaticAssignmentFile - // we shold simply join them with semicolons - return serverConfig.join(';'); -} - void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } -void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) { +void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) { // we have a string for config for this type - qDebug() << "Parsing command line config for assignment type" << type; + qDebug() << "Parsing config for assignment type" << type; - QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts); + int configCounter = 0; - const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*([\\w-]+)"; - QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX); - - // read each config to a payload for this type of assignment - for (int i = 0; i < multiConfigList.size(); i++) { - QString config = multiConfigList.at(i); - - // check the config string for a pool - QString assignmentPool; - - int poolIndex = poolRegex.indexIn(config); - - if (poolIndex != -1) { - assignmentPool = poolRegex.cap(1); + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); - // remove the pool from the config string, the assigned node doesn't need it - config.remove(poolIndex, poolRegex.matchedLength()); + // check the config string for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool; + + QJsonValue poolValue = jsonObject[ASSIGNMENT_POOL_KEY]; + if (!poolValue.isUndefined()) { + assignmentPool = poolValue.toString(); + + jsonObject.remove(ASSIGNMENT_POOL_KEY); + } + + ++configCounter; + qDebug() << "Type" << type << "config" << configCounter << "=" << jsonObject; + + Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); + + // setup the payload as a semi-colon separated list of key = value + QStringList payloadStringList; + foreach(const QString& payloadKey, jsonObject.keys()) { + QString dashes = payloadKey.size() == 1 ? "-" : "--"; + payloadStringList << QString("%1%2 %3").arg(dashes).arg(payloadKey).arg(jsonObject[payloadKey].toString()); + } + + configAssignment->setPayload(payloadStringList.join(' ').toUtf8()); + + addStaticAssignmentToAssignmentHash(configAssignment); } - - qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData()); - - Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); - - configAssignment->setPayload(config.toUtf8()); - - addStaticAssignmentToAssignmentHash(configAssignment); } } @@ -302,21 +306,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetgetNodeSocket().writeDatagram(authenticationRequestPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); -} - const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer; @@ -347,10 +336,14 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe // create a new session UUID for this node QUuid nodeUUID = QUuid::createUuid(); - SharedNodePointer newNode = NodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); + SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); - // when the newNode is created the linked data is also created, if this was a static assignment set the UUID - reinterpret_cast(newNode->getLinkedData())->setStaticAssignmentUUID(assignmentUUID); + // when the newNode is created the linked data is also created + // if this was a static assignment set the UUID, set the sendingSockAddr + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + nodeData->setStaticAssignmentUUID(assignmentUUID); + nodeData->setSendingSockAddr(senderSockAddr); if (!authJsonObject.isEmpty()) { // pull the connection secret from the authJsonObject and set it as the connection secret for this node @@ -430,10 +423,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - NodeList* nodeList = NodeList::getInstance(); - + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); if (nodeInterestList.size() > 0) { + + DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; + unsigned int dataMTU = dtlsSession ? gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; + // if the node has any interest types, send back those nodes as well foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { @@ -463,11 +459,15 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif nodeDataStream << secretUUID; - if (broadcastPacket.size() + nodeByteArray.size() > MAX_PACKET_SIZE) { + if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { // we need to break here and start a new packet // so send the current one - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } // reset the broadcastPacket structure broadcastPacket.resize(numBroadcastPacketLeadBytes); @@ -478,78 +478,180 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif broadcastPacket.append(nodeByteArray); } } + + // always write the last broadcastPacket + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } } - - // always write the last broadcastPacket - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); } void DomainServer::readAvailableDatagrams() { - NodeList* nodeList = NodeList::getInstance(); - AccountManager& accountManager = AccountManager::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); HifiSockAddr senderSockAddr; + QByteArray receivedPacket; static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment); static int numAssignmentPacketHeaderBytes = assignmentPacket.size(); - - QByteArray receivedPacket; while (nodeList->getNodeSocket().hasPendingDatagrams()) { receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - - if (nodeList->packetVersionAndHashMatch(receivedPacket)) { - PacketType requestType = packetTypeForPacket(receivedPacket); + if (packetTypeForPacket(receivedPacket) == PacketTypeRequestAssignment + && nodeList->packetVersionAndHashMatch(receivedPacket)) { + + // construct the requested assignment from the packet data + Assignment requestAssignment(receivedPacket); - if (requestType == PacketTypeDomainConnectRequest) { - QDataStream packetStream(receivedPacket); - packetStream.skipRawData(numBytesForPacketHeader(receivedPacket)); + // Suppress these for Assignment::AgentType to once per 5 seconds + static quint64 lastNoisyMessage = usecTimestampNow(); + quint64 timeNow = usecTimestampNow(); + const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND; + bool noisyMessage = false; + if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + qDebug() << "Received a request for assignment type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; + } + + SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); + + if (assignmentToDeploy) { + qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - quint8 hasRegistrationToken; - packetStream >> hasRegistrationToken; + // give this assignment out, either the type matches or the requestor said they will take any + assignmentPacket.resize(numAssignmentPacketHeaderBytes); - if (requiresAuthentication() && !hasRegistrationToken) { - // we need authentication and this node did not give us a registration token - tell it to auth - requestAuthenticationFromPotentialNode(senderSockAddr); - } else if (requiresAuthentication()) { - QByteArray registrationToken; - packetStream >> registrationToken; - - QString registrationTokenString(registrationToken.toHex()); - QJsonObject jsonForRedeemedToken = _redeemedTokenResponses.value(registrationTokenString); - - // check if we have redeemed this token and are ready to check the node in - if (jsonForRedeemedToken.isEmpty()) { - // make a request against the data-server to get information required to connect to this node - JSONCallbackParameters tokenCallbackParams; - tokenCallbackParams.jsonCallbackReceiver = this; - tokenCallbackParams.jsonCallbackMethod = "processTokenRedeemResponse"; - - QString redeemURLString = QString("/api/v1/nodes/redeem/%1.json").arg(registrationTokenString); - accountManager.authenticatedRequest(redeemURLString, QNetworkAccessManager::GetOperation, - tokenCallbackParams); - } else if (jsonForRedeemedToken["status"].toString() != "success") { - // we redeemed the token, but it was invalid - get the node to get another - requestAuthenticationFromPotentialNode(senderSockAddr); - } else { - // we've redeemed the token for this node and are ready to start communicating with it - // add the node to our NodeList - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr, jsonForRedeemedToken); - } - - // if it exists, remove this response from the in-memory hash - _redeemedTokenResponses.remove(registrationTokenString); - - } else { - // we don't require authentication - add this node to our NodeList - // and send back session UUID right away - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); + + assignmentStream << *assignmentToDeploy.data(); + + nodeList->getNodeSocket().writeDatagram(assignmentPacket, + senderSockAddr.getAddress(), senderSockAddr.getPort()); + } else { + if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; } + } + + if (noisyMessage) { + lastNoisyMessage = timeNow; + } + } else if (!_isUsingDTLS) { + // not using DTLS, process datagram normally + processDatagram(receivedPacket, senderSockAddr); + } else { + // we're using DTLS, so tell the sender to get back to us using DTLS + static QByteArray dtlsRequiredPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerRequireDTLS); + static int numBytesDTLSHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerRequireDTLS); + + if (dtlsRequiredPacket.size() == numBytesDTLSHeader) { + // pack the port that we accept DTLS traffic on + unsigned short dtlsPort = nodeList->getDTLSSocket().localPort(); + dtlsRequiredPacket.replace(numBytesDTLSHeader, sizeof(dtlsPort), reinterpret_cast(&dtlsPort)); + } + + nodeList->writeUnverifiedDatagram(dtlsRequiredPacket, senderSockAddr); + } + } +} + +void DomainServer::readAvailableDTLSDatagrams() { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + QUdpSocket& dtlsSocket = nodeList->getDTLSSocket(); + + static sockaddr senderSockAddr; + static socklen_t sockAddrSize = sizeof(senderSockAddr); + + while (dtlsSocket.hasPendingDatagrams()) { + // check if we have an active DTLS session for this sender + QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0); + + recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(), + MSG_PEEK, &senderSockAddr, &sockAddrSize); + + HifiSockAddr senderHifiSockAddr(&senderSockAddr); + DTLSServerSession* existingSession = _dtlsSessions.value(senderHifiSockAddr); + + if (existingSession) { + if (!existingSession->completedHandshake()) { + // check if we have completed handshake with this user + int handshakeReturn = gnutls_handshake(*existingSession->getGnuTLSSession()); - } else if (requestType == PacketTypeDomainListRequest) { - QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + if (handshakeReturn == 0) { + existingSession->setCompletedHandshake(true); + } else if (gnutls_error_is_fatal(handshakeReturn)) { + // this was a fatal error handshaking, so remove this session + qDebug() << "Fatal error -" << gnutls_strerror(handshakeReturn) << "- during DTLS handshake with" + << senderHifiSockAddr; + _dtlsSessions.remove(senderHifiSockAddr); + } + } else { + // pull the data from this user off the stack and process it + int receivedBytes = gnutls_record_recv(*existingSession->getGnuTLSSession(), + peekDatagram.data(), peekDatagram.size()); + if (receivedBytes > 0) { + processDatagram(peekDatagram.left(receivedBytes), senderHifiSockAddr); + } else if (gnutls_error_is_fatal(receivedBytes)) { + qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- during DTLS handshake with" + << senderHifiSockAddr; + } + } + } else { + // first we verify the cookie + // see http://gnutls.org/manual/html_node/DTLS-sessions.html for why this is required + gnutls_dtls_prestate_st prestate; + memset(&prestate, 0, sizeof(prestate)); + int cookieValid = gnutls_dtls_cookie_verify(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), + peekDatagram.data(), peekDatagram.size(), &prestate); + + if (cookieValid < 0) { + // the cookie sent by the client was not valid + // send a valid one + DummyDTLSSession tempServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), senderHifiSockAddr); + + gnutls_dtls_cookie_send(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), &prestate, + &tempServerSession, DTLSSession::socketPush); + + // acutally pull the peeked data off the network stack so that it gets discarded + dtlsSocket.readDatagram(peekDatagram.data(), peekDatagram.size()); + } else { + // cookie valid but no existing session - set up a new session now + DTLSServerSession* newServerSession = new DTLSServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), + senderHifiSockAddr); + gnutls_session_t* gnutlsSession = newServerSession->getGnuTLSSession(); + + gnutls_priority_set(*gnutlsSession, *_priorityCache); + gnutls_credentials_set(*gnutlsSession, GNUTLS_CRD_CERTIFICATE, *_x509Credentials); + gnutls_dtls_prestate_set(*gnutlsSession, &prestate); + + // handshake to begin the session + gnutls_handshake(*gnutlsSession); + + qDebug() << "Beginning DTLS session with node at" << senderHifiSockAddr; + _dtlsSessions[senderHifiSockAddr] = newServerSession; + } + } + } +} + +void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + PacketType requestType = packetTypeForPacket(receivedPacket); + + if (requestType == PacketTypeDomainListRequest) { + QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + + if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { NodeType_t throwawayNodeType; HifiSockAddr nodePublicAddress, nodeLocalAddress; @@ -557,60 +659,22 @@ void DomainServer::readAvailableDatagrams() { receivedPacket, senderSockAddr); SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress); - + // update last receive to now quint64 timeNow = usecTimestampNow(); checkInNode->setLastHeardMicrostamp(timeNow); - - + sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes)); - - } else if (requestType == PacketTypeRequestAssignment) { - - // construct the requested assignment from the packet data - Assignment requestAssignment(receivedPacket); - - // Suppress these for Assignment::AgentType to once per 5 seconds - static quint64 lastNoisyMessage = usecTimestampNow(); - quint64 timeNow = usecTimestampNow(); - const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND; - bool noisyMessage = false; - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { - qDebug() << "Received a request for assignment type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - - SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); - - if (assignmentToDeploy) { - qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - - // give this assignment out, either the type matches or the requestor said they will take any - assignmentPacket.resize(numAssignmentPacketHeaderBytes); - - QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); - - assignmentStream << *assignmentToDeploy.data(); - - nodeList->getNodeSocket().writeDatagram(assignmentPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); - } else { - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { - qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - } - - if (noisyMessage) { - lastNoisyMessage = timeNow; - } - } else if (requestType == PacketTypeNodeJsonStats) { - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - if (matchingNode) { - reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); - } + } else { + // new node - add this node to our NodeList + // and send back session UUID right away + addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + } + + } else if (requestType == PacketTypeNodeJsonStats) { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); } } } @@ -679,7 +743,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject assignedNodesJSON; // enumerate the NodeList to find the assigned nodes - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (_staticAssignmentHash.value(node->getUUID())) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); @@ -721,7 +785,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonArray nodesJSONArray; // enumerate the NodeList to find the assigned nodes - NodeList* nodeList = NodeList::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // add the node using the UUID as the key @@ -745,7 +809,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUuid matchingUUID = QUuid(nodeShowRegex.cap(1)); // see if we have a node that matches this ID - SharedNodePointer matchingNode = NodeList::getInstance()->nodeWithUUID(matchingUUID); + SharedNodePointer matchingNode = LimitedNodeList::getInstance()->nodeWithUUID(matchingUUID); if (matchingNode) { // create a QJsonDocument with the stats QJsonObject QJsonObject statsObject = @@ -824,14 +888,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // pull the captured string, if it exists QUuid deleteUUID = QUuid(nodeDeleteRegex.cap(1)); - SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); + SharedNodePointer nodeToKill = LimitedNodeList::getInstance()->nodeWithUUID(deleteUUID); if (nodeToKill) { // start with a 200 response connection->respond(HTTPConnection::StatusCode200); // we have a valid UUID and node - kill the node that has this assignment - QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); + QMetaObject::invokeMethod(LimitedNodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); // successfully processed request return true; @@ -840,7 +904,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (allNodesDeleteRegex.indexIn(url.path()) != -1) { qDebug() << "Received request to kill all nodes."; - NodeList::getInstance()->eraseAllNodes(); + LimitedNodeList::getInstance()->eraseAllNodes(); return true; } @@ -875,6 +939,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeKilled(SharedNodePointer node) { DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + if (nodeData) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue if (!nodeData->getStaticAssignmentUUID().isNull()) { @@ -887,11 +952,19 @@ void DomainServer::nodeKilled(SharedNodePointer node) { // cleanup the connection secrets that we set up for this node (on the other nodes) foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { - SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); + SharedNodePointer otherNode = LimitedNodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); if (otherNode) { reinterpret_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); } } + + if (_isUsingDTLS) { + // check if we need to remove a DTLS session from our in-memory hash + DTLSServerSession* existingSession = _dtlsSessions.take(nodeData->getSendingSockAddr()); + if (existingSession) { + delete existingSession; + } + } } } @@ -972,7 +1045,7 @@ void DomainServer::addStaticAssignmentsToQueue() { bool foundMatchingAssignment = false; // enumerate the nodes and check if there is one with an attached assignment with matching UUID - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (node->getUUID() == staticAssignment->data()->getUUID()) { foundMatchingAssignment = true; } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c2f0eee995..1400fadee3 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -20,18 +20,21 @@ #include #include +#include + #include #include #include +#include "DTLSServerSession.h" + typedef QSharedPointer SharedAssignmentPointer; class DomainServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT public: DomainServer(int argc, char* argv[]); - - bool requiresAuthentication() const { return !_nodeAuthenticationURL.isEmpty(); } + ~DomainServer(); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); @@ -43,10 +46,17 @@ public slots: /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); +private slots: + + void readAvailableDatagrams(); + void readAvailableDTLSDatagrams(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + bool optionallySetupDTLS(); + bool readX509KeyAndCertificate(); + + void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); - void requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr); void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr, const QJsonObject& authJsonObject = QJsonObject()); int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, @@ -55,11 +65,9 @@ private: void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, const NodeSet& nodeInterestList); - void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes); - void readConfigFile(const QString& path, QSet& excludedTypes); - QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName); + void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); - void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString); + void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray); void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); @@ -76,17 +84,15 @@ private: QHash _staticAssignmentHash; QQueue _assignmentQueue; - QUrl _nodeAuthenticationURL; + QVariantMap _argumentVariantMap; - QStringList _argumentList; + bool _isUsingDTLS; + gnutls_certificate_credentials_t* _x509Credentials; + gnutls_dh_params_t* _dhParams; + gnutls_datum_t* _cookieKey; + gnutls_priority_t* _priorityCache; - QHash _redeemedTokenResponses; -private slots: - void requestCreationFromDataServer(); - void processCreateResponseFromDataServer(const QJsonObject& jsonObject); - void processTokenRedeemResponse(const QJsonObject& jsonObject); - - void readAvailableDatagrams(); + QHash _dtlsSessions; }; #endif // hifi_DomainServer_h diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index 9e118faff6..59d60659de 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -20,7 +20,8 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), _staticAssignmentUUID(), - _statsJSONObject() + _statsJSONObject(), + _sendingSockAddr() { } diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 3b73ec17d2..6026e65f25 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -15,6 +15,7 @@ #include #include +#include #include class DomainServerNodeData : public NodeData { @@ -29,6 +30,9 @@ public: void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } + const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } + QHash& getSessionSecretHash() { return _sessionSecretHash; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); @@ -36,6 +40,7 @@ private: QHash _sessionSecretHash; QUuid _staticAssignmentUUID; QJsonObject _statsJSONObject; + HifiSockAddr _sendingSockAddr; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 2c8c3bd4c4..bdb10cb56f 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -26,7 +26,7 @@ int main(int argc, char* argv[]) { #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + qInstallMessageHandler(Logging::verboseMessageHandler); DomainServer domainServer(argc, argv); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4e58d898af..6295095b43 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -33,8 +33,8 @@ elseif (UNIX) # include the right GL headers for UNIX set(GL_HEADERS "#include \n#include \n#include ") elseif (WIN32) - add_definitions( -D_USE_MATH_DEFINES ) # apparently needed to get M_PI and other defines from cmath/math.h - add_definitions( -DWINDOWS_LEAN_AND_MEAN ) # needed to make sure windows doesn't go to crazy with its defines + add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h + add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines set(GL_HEADERS "#include \n#include \n#include ") endif () @@ -68,6 +68,7 @@ foreach(EXTERNAL_SOURCE_SUBDIR ${EXTERNAL_SOURCE_SUBDIRS}) endforeach(EXTERNAL_SOURCE_SUBDIR) find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) +find_package(GnuTLS REQUIRED) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) @@ -120,6 +121,7 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}") @@ -190,9 +192,14 @@ endif (QXMPP_FOUND AND NOT DISABLE_QXMPP) # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + # include external library headers # use system flag so warnings are supressed -include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}") +include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") target_link_libraries( ${TARGET_NAME} @@ -200,6 +207,7 @@ target_link_libraries( "${ZLIB_LIBRARIES}" Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools + "${GNUTLS_LIBRARY}" ) if (APPLE) diff --git a/interface/resources/images/close_down.svg b/interface/resources/images/close_down.svg new file mode 100644 index 0000000000..f36865fff1 --- /dev/null +++ b/interface/resources/images/close_down.svg @@ -0,0 +1,79 @@ + + + + + + image/svg+xml + + Slice 1 + + + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e109cbebd5..831dc8506c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,6 +170,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _previousScriptLocation(), _logger(new FileLogger(this)) { + // init GnuTLS for DTLS with domain-servers + DTLSClientSession::globalInit(); + // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -227,8 +230,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : audioThread->start(); - connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -275,7 +278,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); @@ -398,6 +401,8 @@ Application::~Application() { delete _glWidget; AccountManager::getInstance().destroy(); + + DTLSClientSession::globalDeinit(); } void Application::saveSettings() { @@ -3058,8 +3063,7 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() - + " @ " + nodeList->getDomainInfo().getHostname() + buildVersion; - + + " @ " + nodeList->getDomainHandler().getHostname() + buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index f2fed15f65..3b19d98146 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -13,7 +13,6 @@ #define hifi_Audio_h #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index af4825fac8..b25b2a84bb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -826,23 +826,22 @@ void Menu::editPreferences() { } void Menu::goToDomain(const QString newDomain) { - if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) { - + if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect Application::getInstance()->getAvatar()->sendKillAvatar(); // give our nodeList the new domain-server hostname - NodeList::getInstance()->getDomainInfo().setHostname(newDomain); + NodeList::getInstance()->getDomainHandler().setHostname(newDomain); } } void Menu::goToDomainDialog() { - QString currentDomainHostname = NodeList::getInstance()->getDomainInfo().getHostname(); + QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname(); - if (NodeList::getInstance()->getDomainInfo().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { + if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { // add the port to the currentDomainHostname string if it is custom - currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainInfo().getPort())); + currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort())); } QInputDialog domainDialog(Application::getInstance()->getWindow()); @@ -1035,7 +1034,7 @@ void Menu::nameLocation() { connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated); NamedLocation* location = new NamedLocation(locationName, myAvatar->getPosition(), myAvatar->getOrientation(), - NodeList::getInstance()->getDomainInfo().getHostname()); + NodeList::getInstance()->getDomainHandler().getHostname()); manager->createNamedLocation(location); } } @@ -1108,18 +1107,12 @@ void Menu::showChat() { mainWindow->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow()); } if (!_chatWindow->toggleViewAction()->isChecked()) { - int width = _chatWindow->width(); - int y = qMax((mainWindow->height() - _chatWindow->height()) / 2, 0); - _chatWindow->move(mainWindow->width(), y); + const QRect& windowGeometry = mainWindow->geometry(); + _chatWindow->move(windowGeometry.topRight().x() - _chatWindow->width(), + windowGeometry.topRight().y() + (windowGeometry.height() / 2) - (_chatWindow->height() / 2)); + _chatWindow->resize(0, _chatWindow->height()); _chatWindow->toggleViewAction()->trigger(); - - QPropertyAnimation* slideAnimation = new QPropertyAnimation(_chatWindow, "geometry", _chatWindow); - slideAnimation->setStartValue(_chatWindow->geometry()); - slideAnimation->setEndValue(QRect(mainWindow->width() - width, _chatWindow->y(), - width, _chatWindow->height())); - slideAnimation->setDuration(250); - slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); } } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 475e7a1abc..e9d804d227 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -228,14 +228,18 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { _skeletonModel.renderJointCollisionShapes(0.7f); } if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { - getHead()->getFaceModel().updateShapePositions(); - getHead()->getFaceModel().renderJointCollisionShapes(0.7f); + if (shouldRenderHead(cameraPosition, renderMode)) { + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderJointCollisionShapes(0.7f); + } } if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { - getHead()->getFaceModel().updateShapePositions(); - getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); - _skeletonModel.updateShapePositions(); - _skeletonModel.renderBoundingCollisionShapes(0.7f); + if (shouldRenderHead(cameraPosition, renderMode)) { + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); + _skeletonModel.updateShapePositions(); + _skeletonModel.renderBoundingCollisionShapes(0.7f); + } } // quick check before falling into the code below: @@ -344,6 +348,10 @@ void Avatar::renderBody(RenderMode renderMode) { getHand()->render(false); } +bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { + return true; +} + void Avatar::updateJointMappings() { // no-op; joint mappings come from skeleton model } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index ca05e5dbbf..4d68f2168b 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -195,6 +195,7 @@ protected: void renderDisplayName(); virtual void renderBody(RenderMode renderMode); + virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; virtual void updateJointMappings(); diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 4ae27faf79..8c58b73ebd 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -85,7 +85,7 @@ public: const bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected) float getAverageLoudness() const { return _averageLoudness; } - glm::vec3 calculateAverageEyePosition() { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * ONE_HALF; } + glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * ONE_HALF; } /// \return the point about which scaling occurs. glm::vec3 getScalePivot() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 59bb438316..8229611646 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -648,15 +648,20 @@ void MyAvatar::renderBody(RenderMode renderMode) { _skeletonModel.render(1.0f, modelRenderMode); // Render head so long as the camera isn't inside it - const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; - Camera* myCamera = Application::getInstance()->getCamera(); - if (renderMode != NORMAL_RENDER_MODE || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > - RENDER_HEAD_CUTOFF_DISTANCE * _scale)) { + if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { getHead()->render(1.0f, modelRenderMode); } getHand()->render(true); } +const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; + +bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { + const Head* head = getHead(); + return (renderMode != NORMAL_RENDER_MODE) || + (glm::length(cameraPosition - head->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); +} + void MyAvatar::updateThrust(float deltaTime) { // // Gather thrust information from keyboard and sensors to apply to avatar motion @@ -1158,7 +1163,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { QStringList coordinateItems = positionString.split(','); QStringList orientationItems = orientationString.split(','); - NodeList::getInstance()->getDomainInfo().setHostname(domainHostnameString); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); // orient the user to face the target glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 66ab322444..71c74f7c91 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -41,6 +41,7 @@ public: void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); void renderBody(RenderMode renderMode); + bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; void renderDebugBodyPoints(); void renderHeadMouse() const; diff --git a/interface/src/starfield/Controller.cpp b/interface/src/starfield/Controller.cpp index b50c393f00..771029c689 100755 --- a/interface/src/starfield/Controller.cpp +++ b/interface/src/starfield/Controller.cpp @@ -10,7 +10,6 @@ // #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/starfield/Generator.cpp b/interface/src/starfield/Generator.cpp index 15d1736e4e..b18e1834be 100644 --- a/interface/src/starfield/Generator.cpp +++ b/interface/src/starfield/Generator.cpp @@ -10,7 +10,6 @@ // #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/ui/BandwidthMeter.h b/interface/src/ui/BandwidthMeter.h index 45226c8e82..6838f28c70 100644 --- a/interface/src/ui/BandwidthMeter.h +++ b/interface/src/ui/BandwidthMeter.h @@ -13,7 +13,6 @@ #define hifi_BandwidthMeter_h #ifdef _WIN32 -#define WANT_TIMEVAL #include #endif diff --git a/interface/src/ui/ChatMessageArea.cpp b/interface/src/ui/ChatMessageArea.cpp new file mode 100644 index 0000000000..f15b788990 --- /dev/null +++ b/interface/src/ui/ChatMessageArea.cpp @@ -0,0 +1,44 @@ +// +// ChatMessageArea.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 4/11/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ChatMessageArea.h" +#include +#include + +ChatMessageArea::ChatMessageArea() : QTextBrowser() { + connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, + this, &ChatMessageArea::updateLayout); +} + +void ChatMessageArea::setHtml(const QString& html) { + // Create format with updated line height + QTextBlockFormat format; + format.setLineHeight(CHAT_MESSAGE_LINE_HEIGHT, QTextBlockFormat::ProportionalHeight); + + // Possibly a bug in QT, the format won't take effect if `insertHtml` is used first. Inserting a space and deleting + // it after ensures the format is applied. + QTextCursor cursor = textCursor(); + cursor.setBlockFormat(format); + cursor.insertText(" "); + cursor.insertHtml(html); + cursor.setPosition(0); + cursor.deleteChar(); +} + +void ChatMessageArea::updateLayout() { + setFixedHeight(document()->size().height()); + updateGeometry(); +} + +void ChatMessageArea::wheelEvent(QWheelEvent* event) { + // Capture wheel events to stop Ctrl-WheelUp/Down zooming + event->ignore(); +} diff --git a/interface/src/ui/ChatMessageArea.h b/interface/src/ui/ChatMessageArea.h new file mode 100644 index 0000000000..1c49c60b08 --- /dev/null +++ b/interface/src/ui/ChatMessageArea.h @@ -0,0 +1,33 @@ +// +// ChatMessageArea.h +// interface/src/ui +// +// Created by Ryan Huffman on 4/11/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ChatMessageArea_h +#define hifi_ChatMessageArea_h + +#include + +const int CHAT_MESSAGE_LINE_HEIGHT = 130; + +class ChatMessageArea : public QTextBrowser { + Q_OBJECT +public: + ChatMessageArea(); + virtual void setHtml(const QString& html); + +public slots: + void updateLayout(); + +protected: + virtual void wheelEvent(QWheelEvent* event); + +}; + +#endif // hifi_ChatMessageArea_h diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 8a83ba680b..635f1f3d10 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -24,6 +24,7 @@ #include "qtimespan.h" #include "ui_chatWindow.h" #include "XmppClient.h" +#include "ChatMessageArea.h" #include "ChatWindow.h" @@ -33,7 +34,9 @@ const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); ChatWindow::ChatWindow() : ui(new Ui::ChatWindow), - numMessagesAfterLastTimeStamp(0) + numMessagesAfterLastTimeStamp(0), + _mousePressed(false), + _mouseStartPosition() { ui->setupUi(this); @@ -86,6 +89,25 @@ ChatWindow::~ChatWindow() { delete ui; } +void ChatWindow::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton && isFloating()) { + _mousePressed = true; + _mouseStartPosition = e->pos(); + } +} + +void ChatWindow::mouseMoveEvent(QMouseEvent *e) { + if (_mousePressed) { + move(mapToParent(e->pos() - _mouseStartPosition)); + } +} + +void ChatWindow::mouseReleaseEvent( QMouseEvent *e ) { + if ( e->button() == Qt::LeftButton ) { + _mousePressed = false; + } +} + void ChatWindow::keyPressEvent(QKeyEvent* event) { QDockWidget::keyPressEvent(event); if (event->key() == Qt::Key_Escape) { @@ -158,8 +180,18 @@ void ChatWindow::addTimeStamp() { "padding: 4px;"); timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); timeLabel->setAlignment(Qt::AlignHCenter); + + bool atBottom = isAtBottom(); + ui->messagesGridLayout->addWidget(timeLabel, ui->messagesGridLayout->rowCount(), 0, 1, 2); + ui->messagesGridLayout->parentWidget()->updateGeometry(); + + Application::processEvents(); numMessagesAfterLastTimeStamp = 0; + + if (atBottom) { + scrollToBottom(); + } } } @@ -235,25 +267,41 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { userLabel->setStyleSheet("padding: 2px; font-weight: bold"); userLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); - QLabel* messageLabel = new QLabel(message.body().replace(regexLinks, "\\1")); - messageLabel->setWordWrap(true); - messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - messageLabel->setOpenExternalLinks(true); - messageLabel->setStyleSheet("padding-bottom: 2px; padding-left: 2px; padding-top: 2px; padding-right: 20px"); - messageLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + ChatMessageArea* messageArea = new ChatMessageArea(); - if (getParticipantName(message.from()) == AccountManager::getInstance().getUsername()) { + messageArea->setOpenLinks(true); + messageArea->setOpenExternalLinks(true); + messageArea->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + messageArea->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + messageArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + messageArea->setReadOnly(true); + + messageArea->setStyleSheet("padding-bottom: 2px;" + "padding-left: 2px;" + "padding-top: 2px;" + "padding-right: 20px;" + "background-color: rgba(0, 0, 0, 0%);" + "border: 0;"); + + bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); + if (fromSelf) { userLabel->setStyleSheet(userLabel->styleSheet() + "; background-color: #e1e8ea"); - messageLabel->setStyleSheet(messageLabel->styleSheet() + "; background-color: #e1e8ea"); + messageArea->setStyleSheet(messageArea->styleSheet() + "; background-color: #e1e8ea"); } + messageArea->setHtml(message.body().replace(regexLinks, "\\1")); + + bool atBottom = isAtBottom(); ui->messagesGridLayout->addWidget(userLabel, ui->messagesGridLayout->rowCount(), 0); - ui->messagesGridLayout->addWidget(messageLabel, ui->messagesGridLayout->rowCount() - 1, 1); + ui->messagesGridLayout->addWidget(messageArea, ui->messagesGridLayout->rowCount() - 1, 1); + ui->messagesGridLayout->parentWidget()->updateGeometry(); Application::processEvents(); - QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); - verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); - messageLabel->updateGeometry(); + + if (atBottom || fromSelf) { + scrollToBottom(); + } ++numMessagesAfterLastTimeStamp; if (message.stamp().isValid()) { @@ -265,6 +313,17 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { #endif +bool ChatWindow::isAtBottom() { + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + return verticalScrollBar->sliderPosition() == verticalScrollBar->maximum(); +} + +// Scroll chat message area to bottom. +void ChatWindow::scrollToBottom() { + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); +} + void ChatWindow::togglePinned() { QMainWindow* mainWindow = Application::getInstance()->getWindow(); mainWindow->removeDockWidget(this); @@ -276,4 +335,4 @@ void ChatWindow::togglePinned() { } this->setFloating(!ui->togglePinnedButton->isChecked()); setTitleBarWidget(ui->togglePinnedButton->isChecked()?new QWidget():titleBar); -} \ No newline at end of file +} diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 614afc1ef1..6a807f9b81 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -39,6 +39,10 @@ public: virtual void keyPressEvent(QKeyEvent *event); virtual void showEvent(QShowEvent* event); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + protected: bool eventFilter(QObject* sender, QEvent* event); @@ -48,11 +52,15 @@ private: #endif void startTimerForTimeStamps(); void addTimeStamp(); + bool isAtBottom(); + void scrollToBottom(); Ui::ChatWindow* ui; QWidget* titleBar; int numMessagesAfterLastTimeStamp; QDateTime lastMessageStamp; + bool _mousePressed; + QPoint _mouseStartPosition; private slots: void connected(); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 5360924e63..77b19373f4 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -79,7 +79,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { shot.setText(ORIENTATION_Z, QString::number(orientation.z)); shot.setText(ORIENTATION_W, QString::number(orientation.w)); - shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainInfo().getHostname()); + shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainHandler().getHostname()); QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z); // replace decimal . with '-' diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 9f1f0527b8..0372e00c09 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -135,13 +135,23 @@ Qt::NoFocus + + QPushButton { + background-color: rgba( 0, 0, 0, 0% ); + border: none; + image: url(../resources/images/close.svg) +} + + +QPushButton:pressed { + background-color: rgba( 0, 0, 0, 0% ); + border: none; + image: url(../resources/images/close_down.svg) +} + - - - ../resources/images/close.svg../resources/images/close.svg - true diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 8d2feb22a2..d946ae5b34 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -20,4 +20,16 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") \ No newline at end of file +link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link GnuTLS +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 420576ece9..001f863e03 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include "AudioRingBuffer.h" diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index 4cc97e56a5..3fa1ebcfe2 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -26,5 +26,15 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index acc060b270..e2a90cb085 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -20,8 +20,19 @@ auto_mtc(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}") +# link in the networking library +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +find_package(GnuTLS REQUIRED) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 9fadef8afc..f1f60e4d87 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include "DatagramSequencer.h" #include "MetavoxelMessages.h" diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt new file mode 100644 index 0000000000..6d805cae6c --- /dev/null +++ b/libraries/networking/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(ROOT_DIR ../..) +set(MACRO_DIR "${ROOT_DIR}/cmake/macros") + +set(TARGET_NAME networking) +project(${TARGET_NAME}) + +find_package(Qt5 COMPONENTS Network) +find_package(GnuTLS REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +# include GLM +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} "${ROOT_DIR}") + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/shared/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp similarity index 99% rename from libraries/shared/src/AccountManager.cpp rename to libraries/networking/src/AccountManager.cpp index 6c0bcb33cb..d1e4edf2ea 100644 --- a/libraries/shared/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -1,6 +1,6 @@ // // AccountManager.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/AccountManager.h b/libraries/networking/src/AccountManager.h similarity index 99% rename from libraries/shared/src/AccountManager.h rename to libraries/networking/src/AccountManager.h index fed6a61772..cb76786f4e 100644 --- a/libraries/shared/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -1,6 +1,6 @@ // // AccountManager.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp similarity index 99% rename from libraries/shared/src/Assignment.cpp rename to libraries/networking/src/Assignment.cpp index 4d6740b400..dd318aad8e 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -1,6 +1,6 @@ // // Assignment.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Assignment.h b/libraries/networking/src/Assignment.h similarity index 99% rename from libraries/shared/src/Assignment.h rename to libraries/networking/src/Assignment.h index c5a83cf417..041121f760 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -1,6 +1,6 @@ // // Assignment.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/networking/src/DTLSClientSession.cpp b/libraries/networking/src/DTLSClientSession.cpp new file mode 100644 index 0000000000..72384fbd10 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.cpp @@ -0,0 +1,83 @@ +// +// DTLSClientSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DomainHandler.h" + +#include "DTLSClientSession.h" + +gnutls_certificate_credentials_t DTLSClientSession::_x509CACredentials; + +void DTLSClientSession::globalInit() { + static bool initialized = false; + + if (!initialized) { + gnutls_global_init(); + gnutls_certificate_allocate_credentials(&_x509CACredentials); + + gnutls_certificate_set_x509_trust_mem(_x509CACredentials, DTLSSession::highFidelityCADatum(), GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_verify_function(_x509CACredentials, DTLSClientSession::verifyServerCertificate); + } +} + +void DTLSClientSession::globalDeinit() { + gnutls_certificate_free_credentials(_x509CACredentials); + + gnutls_global_deinit(); +} + +int DTLSClientSession::verifyServerCertificate(gnutls_session_t session) { + unsigned int verifyStatus = 0; + + // grab the hostname from the domain handler that this session is associated with + DomainHandler* domainHandler = reinterpret_cast(gnutls_session_get_ptr(session)); + qDebug() << "Checking for" << domainHandler->getHostname() << "from cert."; + + int certReturn = gnutls_certificate_verify_peers3(session, + domainHandler->getHostname().toLocal8Bit().constData(), + &verifyStatus); + + if (certReturn < 0) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + + gnutls_certificate_type_t typeReturn = gnutls_certificate_type_get(session); + + gnutls_datum_t printOut; + + certReturn = gnutls_certificate_verification_status_print(verifyStatus, typeReturn, &printOut, 0); + + if (certReturn < 0) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + + qDebug() << "Gnutls certificate verification status:" << reinterpret_cast(printOut.data); + +#ifdef WIN32 + free(printOut.data); +#else + gnutls_free(printOut.data); +#endif + + if (verifyStatus != 0) { + qDebug() << "Server provided certificate for DTLS is not trusted. Can not complete handshake."; + return GNUTLS_E_CERTIFICATE_ERROR; + } else { + // certificate is valid, continue handshaking as normal + return 0; + } +} + +DTLSClientSession::DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DTLSSession(GNUTLS_CLIENT, dtlsSocket, destinationSocket) +{ + gnutls_priority_set_direct(_gnutlsSession, "PERFORMANCE", NULL); + gnutls_credentials_set(_gnutlsSession, GNUTLS_CRD_CERTIFICATE, _x509CACredentials); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSClientSession.h b/libraries/networking/src/DTLSClientSession.h new file mode 100644 index 0000000000..eeca1aaa68 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.h @@ -0,0 +1,30 @@ +// +// DTLSClientSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSClientSession_h +#define hifi_DTLSClientSession_h + +#include "DTLSSession.h" + +class DTLSClientSession : public DTLSSession { +public: + DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); + + static void globalInit(); + static void globalDeinit(); + + static int verifyServerCertificate(gnutls_session_t session); + + static gnutls_certificate_credentials_t _x509CACredentials; + static bool _wasGloballyInitialized; +}; + +#endif // hifi_DTLSClientSession_h diff --git a/libraries/networking/src/DTLSSession.cpp b/libraries/networking/src/DTLSSession.cpp new file mode 100644 index 0000000000..7d375ec327 --- /dev/null +++ b/libraries/networking/src/DTLSSession.cpp @@ -0,0 +1,145 @@ +// +// DTLSSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "NodeList.h" +#include "DTLSSession.h" + +int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) { + DTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + + if (dtlsSocket.hasPendingDatagrams()) { + // peek the data on stack to see if it belongs to this session + static sockaddr senderSockAddr; + static socklen_t sockAddrSize = sizeof(senderSockAddr); + + QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0); + + recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(), + MSG_PEEK, &senderSockAddr, &sockAddrSize); + + if (HifiSockAddr(&senderSockAddr) == session->_destinationSocket) { + // there is data for this session ready to be read + return 1; + } else { + // the next data from the dtlsSocket is not for this session + return 0; + } + } else { + // no data available on the dtlsSocket + return 0; + } +} + +ssize_t DTLSSession::socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size) { + DTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + + HifiSockAddr pulledSockAddr; + qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast(buffer), size, + pulledSockAddr.getAddressPointer(), pulledSockAddr.getPortPointer()); + if (bytesReceived == -1) { + // no data to pull, return -1 +#if DTLS_VERBOSE_DEBUG + qDebug() << "Received no data on call to readDatagram"; +#endif + return bytesReceived; + } + + if (pulledSockAddr == session->_destinationSocket) { + // bytes received from the correct sender, return number of bytes received + +#if DTLS_VERBOSE_DEBUG + qDebug() << "Received" << bytesReceived << "on DTLS socket from" << pulledSockAddr; +#endif + + return bytesReceived; + } + + // we pulled a packet not matching this session, so output that + qDebug() << "Denied connection from" << pulledSockAddr; + + gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN); + return -1; +} + +gnutls_datum_t* DTLSSession::highFidelityCADatum() { + static gnutls_datum_t hifiCADatum; + static bool datumInitialized = false; + + static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] = + "-----BEGIN CERTIFICATE-----" + "MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n" + "VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n" + "aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n" + "YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n" + "E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n" + "MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n" + "BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n" + "MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n" + "IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n" + "AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n" + "2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n" + "wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n" + "AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n" + "HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n" + "EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n" + "bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n" + "b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n" + "c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n" + "SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n" + "FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n" + "Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n" + "-----END CERTIFICATE-----"; + + if (!datumInitialized) { + hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT; + hifiCADatum.size = sizeof(HIGHFIDELITY_ROOT_CA_CERT); + } + + return &hifiCADatum; +} + +DTLSSession::DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) : + DummyDTLSSession(dtlsSocket, destinationSocket), + _completedHandshake(false) +{ + gnutls_init(&_gnutlsSession, end | GNUTLS_DATAGRAM | GNUTLS_NONBLOCK); + + // see http://gnutls.org/manual/html_node/Datagram-TLS-API.html#gnutls_005fdtls_005fset_005fmtu + const unsigned int DTLS_MAX_MTU = 1452; + gnutls_dtls_set_mtu(_gnutlsSession, DTLS_MAX_MTU); + + const unsigned int DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT = DOMAIN_SERVER_CHECK_IN_MSECS; + const unsigned int DTLS_TOTAL_CONNECTION_TIMEOUT = 2 * NODE_SILENCE_THRESHOLD_MSECS; + gnutls_dtls_set_timeouts(_gnutlsSession, DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT, DTLS_TOTAL_CONNECTION_TIMEOUT); + + gnutls_transport_set_ptr(_gnutlsSession, this); + gnutls_transport_set_push_function(_gnutlsSession, DummyDTLSSession::socketPush); + gnutls_transport_set_pull_function(_gnutlsSession, socketPull); + gnutls_transport_set_pull_timeout_function(_gnutlsSession, socketPullTimeout); +} + +DTLSSession::~DTLSSession() { + gnutls_deinit(_gnutlsSession); +} + +void DTLSSession::setCompletedHandshake(bool completedHandshake) { + _completedHandshake = completedHandshake; + qDebug() << "Completed DTLS handshake with" << _destinationSocket; +} + +qint64 DTLSSession::writeDatagram(const QByteArray& datagram) { + // we don't need to put a hash in this packet, so just send it off + return gnutls_record_send(_gnutlsSession, datagram.data(), datagram.size()); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSSession.h b/libraries/networking/src/DTLSSession.h new file mode 100644 index 0000000000..9e9542e147 --- /dev/null +++ b/libraries/networking/src/DTLSSession.h @@ -0,0 +1,44 @@ +// +// DTLSSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-01. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DTLSSession_h +#define hifi_DTLSSession_h + +#include + +#include + +#include "DummyDTLSSession.h" +#include "HifiSockAddr.h" + +class DTLSSession : public DummyDTLSSession { + Q_OBJECT +public: + DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); + ~DTLSSession(); + + static int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms); + static ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size); + + static gnutls_datum_t* highFidelityCADatum(); + + qint64 writeDatagram(const QByteArray& datagram); + + gnutls_session_t* getGnuTLSSession() { return &_gnutlsSession; } + + bool completedHandshake() const { return _completedHandshake; } + void setCompletedHandshake(bool completedHandshake); +protected: + gnutls_session_t _gnutlsSession; + bool _completedHandshake; +}; + +#endif // hifi_DTLSSession_h diff --git a/libraries/shared/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp similarity index 98% rename from libraries/shared/src/DataServerAccountInfo.cpp rename to libraries/networking/src/DataServerAccountInfo.cpp index 1c53bca30f..87d3b694a7 100644 --- a/libraries/shared/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -1,6 +1,6 @@ // // DataServerAccountInfo.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h similarity index 97% rename from libraries/shared/src/DataServerAccountInfo.h rename to libraries/networking/src/DataServerAccountInfo.h index ae5de6de64..21380c0855 100644 --- a/libraries/shared/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -1,6 +1,6 @@ // // DataServerAccountInfo.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/21/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp new file mode 100644 index 0000000000..6c2fb0274a --- /dev/null +++ b/libraries/networking/src/DomainHandler.cpp @@ -0,0 +1,183 @@ +// +// DomainHandler.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/18/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "NodeList.h" +#include "PacketHeaders.h" + +#include "DomainHandler.h" + +DomainHandler::DomainHandler(QObject* parent) : + QObject(parent), + _uuid(), + _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), + _assignmentUUID(), + _isConnected(false), + _dtlsSession(NULL), + _handshakeTimer(NULL) +{ + +} + +DomainHandler::~DomainHandler() { + delete _dtlsSession; +} + +void DomainHandler::clearConnectionInfo() { + _uuid = QUuid(); + _isConnected = false; + + if (_handshakeTimer) { + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + } + + delete _dtlsSession; + _dtlsSession = NULL; +} + +void DomainHandler::reset() { + clearConnectionInfo(); + _hostname = QString(); + _sockAddr.setAddress(QHostAddress::Null); + + delete _dtlsSession; + _dtlsSession = NULL; +} + +const unsigned int DTLS_HANDSHAKE_INTERVAL_MSECS = 100; + +void DomainHandler::initializeDTLSSession() { + if (!_dtlsSession) { + _dtlsSession = new DTLSClientSession(NodeList::getInstance()->getDTLSSocket(), _sockAddr); + + gnutls_session_set_ptr(*_dtlsSession->getGnuTLSSession(), this); + + // start a timer to complete the handshake process + _handshakeTimer = new QTimer(this); + connect(_handshakeTimer, &QTimer::timeout, this, &DomainHandler::completeDTLSHandshake); + + // start the handshake right now + completeDTLSHandshake(); + + // start the timer to finish off the handshake + _handshakeTimer->start(DTLS_HANDSHAKE_INTERVAL_MSECS); + } +} + +void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { + if (_sockAddr != sockAddr) { + // we should reset on a sockAddr change + reset(); + // change the sockAddr + _sockAddr = sockAddr; + } + + // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification + _hostname = hostname; +} + +void DomainHandler::setHostname(const QString& hostname) { + + if (hostname != _hostname) { + // re-set the domain info so that auth information is reloaded + reset(); + + int colonIndex = hostname.indexOf(':'); + + if (colonIndex > 0) { + // the user has included a custom DS port with the hostname + + // the new hostname is everything up to the colon + _hostname = hostname.left(colonIndex); + + // grab the port by reading the string after the colon + _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); + + qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); + + } else { + // no port included with the hostname, simply set the member variable and reset the domain server port to default + _hostname = hostname; + _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); + } + + // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname + qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); + QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + + emit hostnameChanged(_hostname); + } +} + +void DomainHandler::completeDTLSHandshake() { + int handshakeReturn = gnutls_handshake(*_dtlsSession->getGnuTLSSession()); + + if (handshakeReturn == 0) { + // we've shaken hands, so we're good to go now + _dtlsSession->setCompletedHandshake(true); + + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + + // emit a signal so NodeList can handle incoming DTLS packets + emit completedDTLSHandshake(); + + } else if (gnutls_error_is_fatal(handshakeReturn)) { + // this was a fatal error handshaking, so remove this session + qDebug() << "Fatal error -" << gnutls_strerror(handshakeReturn) + << "- during DTLS handshake with DS at" + << qPrintable((_hostname.isEmpty() ? _sockAddr.getAddress().toString() : _hostname)); + + clearConnectionInfo(); + } +} + +void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { + for (int i = 0; i < hostInfo.addresses().size(); i++) { + if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { + _sockAddr.setAddress(hostInfo.addresses()[i]); + qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), + _sockAddr.getAddress().toString().toLocal8Bit().constData()); + return; + } + } + + // if we got here then we failed to lookup the address + qDebug("Failed domain server lookup"); +} + +void DomainHandler::setIsConnected(bool isConnected) { + if (_isConnected != isConnected) { + _isConnected = isConnected; + + if (_isConnected) { + emit connectedToDomain(_hostname); + } + } +} + +void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) { + // figure out the port that the DS wants us to use for us to talk to them with DTLS + int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket); + + unsigned short dtlsPort = 0; + memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort)); + + qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS."; + + _sockAddr.setPort(dtlsPort); + + initializeDTLSSession(); +} \ No newline at end of file diff --git a/libraries/shared/src/DomainInfo.h b/libraries/networking/src/DomainHandler.h similarity index 65% rename from libraries/shared/src/DomainInfo.h rename to libraries/networking/src/DomainHandler.h index b52b2f71c3..2cc520991c 100644 --- a/libraries/shared/src/DomainInfo.h +++ b/libraries/networking/src/DomainHandler.h @@ -1,6 +1,6 @@ // -// DomainInfo.h -// libraries/shared/src +// DomainHandler.h +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. @@ -9,28 +9,30 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_DomainInfo_h -#define hifi_DomainInfo_h +#ifndef hifi_DomainHandler_h +#define hifi_DomainHandler_h #include +#include #include #include #include +#include "DTLSClientSession.h" #include "HifiSockAddr.h" const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; +const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; -class DomainInfo : public QObject { +class DomainHandler : public QObject { Q_OBJECT public: - DomainInfo(); + DomainHandler(QObject* parent = 0); + ~DomainHandler(); void clearConnectionInfo(); - void parseAuthInformationFromJsonObject(const QJsonObject& jsonObject); - const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } @@ -41,43 +43,40 @@ public: void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } const HifiSockAddr& getSockAddr() { return _sockAddr; } - void setSockAddr(const HifiSockAddr& sockAddr); + void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname); unsigned short getPort() const { return _sockAddr.getPort(); } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - const QUuid& getConnectionSecret() const { return _connectionSecret; } - void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } - - const QByteArray& getRegistrationToken() const { return _registrationToken; } - - const QUrl& getRootAuthenticationURL() const { return _rootAuthenticationURL; } - void setRootAuthenticationURL(const QUrl& rootAuthenticationURL) { _rootAuthenticationURL = rootAuthenticationURL; } - bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); -private slots: - void completedHostnameLookup(const QHostInfo& hostInfo); + DTLSClientSession* getDTLSSession() { return _dtlsSession; } - void logout(); + void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); + +private slots: + void completeDTLSHandshake(); + void completedHostnameLookup(const QHostInfo& hostInfo); signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); + void completedDTLSHandshake(); + void DTLSConnectionLost(); + private: void reset(); + void initializeDTLSSession(); QUuid _uuid; QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; - QUuid _connectionSecret; - QByteArray _registrationToken; - QUrl _rootAuthenticationURL; - QString _publicKey; bool _isConnected; + DTLSClientSession* _dtlsSession; + QTimer* _handshakeTimer; }; -#endif // hifi_DomainInfo_h +#endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/DummyDTLSSession.cpp b/libraries/networking/src/DummyDTLSSession.cpp new file mode 100644 index 0000000000..a42e3f1599 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.cpp @@ -0,0 +1,31 @@ +// +// DummyDTLSSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DummyDTLSSession.h" + +ssize_t DummyDTLSSession::socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size) { + DummyDTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + +#if DTLS_VERBOSE_DEBUG + qDebug() << "Pushing a message of size" << size << "to" << session->_destinationSocket; +#endif + + return dtlsSocket.writeDatagram(reinterpret_cast(buffer), size, + session->_destinationSocket.getAddress(), session->_destinationSocket.getPort()); +} + +DummyDTLSSession::DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket) : + _dtlsSocket(dtlsSocket), + _destinationSocket(destinationSocket) +{ + +} \ No newline at end of file diff --git a/libraries/networking/src/DummyDTLSSession.h b/libraries/networking/src/DummyDTLSSession.h new file mode 100644 index 0000000000..352b2c23e2 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.h @@ -0,0 +1,34 @@ +// +// DummyDTLSSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DummyDTLSSession_h +#define hifi_DummyDTLSSession_h + +#include + +#include + +#include "HifiSockAddr.h" + +#define DTLS_VERBOSE_DEBUG 0 + +class DummyDTLSSession : public QObject { + Q_OBJECT +public: + DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket); + + static ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size); +protected: + QUdpSocket& _dtlsSocket; + HifiSockAddr _destinationSocket; +}; + +#endif // hifi_DummyDTLSSession_h diff --git a/libraries/shared/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp similarity index 76% rename from libraries/shared/src/HifiSockAddr.cpp rename to libraries/networking/src/HifiSockAddr.cpp index 25e9e5637b..d30f7944d7 100644 --- a/libraries/shared/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -1,6 +1,6 @@ // // HifiSockAddr.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. // Copyright 2013 High Fidelity, Inc. @@ -9,12 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "HifiSockAddr.h" - #include #include #include +#include "HifiSockAddr.h" + static int hifiSockAddrMetaTypeId = qMetaTypeId(); HifiSockAddr::HifiSockAddr() : @@ -47,6 +47,16 @@ HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { } } +HifiSockAddr::HifiSockAddr(const sockaddr* sockaddr) { + _address = QHostAddress(sockaddr); + + if (sockaddr->sa_family == AF_INET) { + _port = ntohs(reinterpret_cast(sockaddr)->sin_port); + } else { + _port = ntohs(reinterpret_cast(sockaddr)->sin6_port); + } +} + HifiSockAddr& HifiSockAddr::operator=(const HifiSockAddr& rhsSockAddr) { _address = rhsSockAddr._address; _port = rhsSockAddr._port; @@ -85,13 +95,13 @@ quint32 getHostOrderLocalAddress() { static int localAddress = 0; if (localAddress == 0) { - foreach(const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - if (interface.flags() & QNetworkInterface::IsUp - && interface.flags() & QNetworkInterface::IsRunning - && interface.flags() & ~QNetworkInterface::IsLoopBack) { + foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.flags() & QNetworkInterface::IsUp + && networkInterface.flags() & QNetworkInterface::IsRunning + && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { // we've decided that this is the active NIC // enumerate it's addresses to grab the IPv4 address - foreach(const QNetworkAddressEntry &entry, interface.addressEntries()) { + foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { // make sure it's an IPv4 address that isn't the loopback if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { qDebug("Node's local address is %s", entry.ip().toString().toLocal8Bit().constData()); @@ -112,3 +122,8 @@ quint32 getHostOrderLocalAddress() { // return the looked up local address return localAddress; } + +uint qHash(const HifiSockAddr& key, uint seed) { + // use the existing QHostAddress and quint16 hash functions to get our hash + return qHash(key.getAddress(), seed) + qHash(key.getPort(), seed); +} diff --git a/libraries/shared/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h similarity index 89% rename from libraries/shared/src/HifiSockAddr.h rename to libraries/networking/src/HifiSockAddr.h index f44ebf6dbd..8a591a60b8 100644 --- a/libraries/shared/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -1,6 +1,6 @@ // // HifiSockAddr.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. // Copyright 2013 High Fidelity, Inc. @@ -12,6 +12,13 @@ #ifndef hifi_HifiSockAddr_h #define hifi_HifiSockAddr_h +#ifdef WIN32 +#include +#include +#else +#include +#endif + #include class HifiSockAddr { @@ -20,6 +27,7 @@ public: HifiSockAddr(const QHostAddress& address, quint16 port); HifiSockAddr(const HifiSockAddr& otherSockAddr); HifiSockAddr(const QString& hostname, quint16 hostOrderPort); + HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } @@ -48,6 +56,8 @@ private: quint16 _port; }; +uint qHash(const HifiSockAddr& key, uint seed); + quint32 getHostOrderLocalAddress(); Q_DECLARE_METATYPE(HifiSockAddr) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp new file mode 100644 index 0000000000..b0b048dde4 --- /dev/null +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -0,0 +1,444 @@ +// +// LimitedNodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "LimitedNodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +const char SOLO_NODE_TYPES[2] = { + NodeType::AvatarMixer, + NodeType::AudioMixer +}; + +const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); + +LimitedNodeList* LimitedNodeList::_sharedInstance = NULL; + +LimitedNodeList* LimitedNodeList::createInstance(unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new LimitedNodeList(socketListenPort, dtlsPort); + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("LimitedNodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +LimitedNodeList* LimitedNodeList::getInstance() { + if (!_sharedInstance) { + qDebug("LimitedNodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + + +LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : + _sessionUUID(), + _nodeHash(), + _nodeHashMutex(QMutex::Recursive), + _nodeSocket(this), + _dtlsSocket(NULL), + _numCollectedPackets(0), + _numCollectedBytes(0), + _packetStatTimer() +{ + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); + qDebug() << "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); + qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + const int LARGER_SNDBUF_SIZE = 1048576; + changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); + + _packetStatTimer.start(); +} + +void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { + QUuid oldUUID = _sessionUUID; + _sessionUUID = sessionUUID; + + if (sessionUUID != oldUUID) { + qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) + << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); + emit uuidChanged(sessionUUID); + } +} + +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); + qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + return *_dtlsSocket; +} + +void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) { + // change the socket send buffer size to be 1MB + int oldBufferSize = 0; + +#ifdef Q_OS_WIN + int sizeOfInt = sizeof(oldBufferSize); +#else + unsigned int sizeOfInt = sizeof(oldBufferSize); +#endif + + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); + + setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), + sizeof(numSendBytes)); + + int newBufferSize = 0; + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); + + qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; +} + +bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { + PacketType checkType = packetTypeForPacket(packet); + if (packet[1] != versionForPacketType(checkType) + && checkType != PacketTypeStunResponse) { + PacketType mismatchType = packetTypeForPacket(packet); + int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); + + static QMultiMap versionDebugSuppressMap; + + QUuid senderUUID = uuidFromPacketHeader(packet); + if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { + qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" + << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" + << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; + + versionDebugSuppressMap.insert(senderUUID, checkType); + } + + return false; + } + + if (!NON_VERIFIED_PACKETS.contains(checkType)) { + // figure out which node this is from + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + if (sendingNode) { + // check if the md5 hash in the header matches the hash we would expect + if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { + return true; + } else { + qDebug() << "Packet hash mismatch on" << checkType << "- Sender" + << uuidFromPacketHeader(packet); + } + } else { + qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" + << uuidFromPacketHeader(packet); + } + } else { + return true; + } + + return false; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, + const QUuid& connectionSecret) { + QByteArray datagramCopy = datagram; + + if (!connectionSecret.isNull()) { + // setup the MD5 hash for source verification in the header + replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); + } + + // stat collection for packets + ++_numCollectedPackets; + _numCollectedBytes += datagram.size(); + + qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + + if (bytesWritten < 0) { + qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); + } + + return bytesWritten; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + if (destinationNode) { + // if we don't have an ovveriden address, assume they want to send to the node's active socket + const HifiSockAddr* destinationSockAddr = &overridenSockAddr; + if (overridenSockAddr.isNull()) { + if (destinationNode->getActiveSocket()) { + // use the node's active socket as the destination socket + destinationSockAddr = destinationNode->getActiveSocket(); + } else { + // we don't have a socket to send to, return 0 + return 0; + } + } + + writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); + } + + // didn't have a destinationNode to send to, return 0 + return 0; +} + +qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr) { + return writeDatagram(datagram, destinationSockAddr, QUuid()); +} + +qint64 LimitedNodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); +} + +void LimitedNodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + // the node decided not to do anything with this packet + // if it comes from a known source we should keep that node alive + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + } +} + +int LimitedNodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { + QMutexLocker locker(&matchingNode->getMutex()); + + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + matchingNode->recordBytesReceived(packet.size()); + + if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { + linkedDataCreateCallback(matchingNode.data()); + } + + QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); + + return matchingNode->getLinkedData()->parseData(packet); +} + +int LimitedNodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + + if (matchingNode) { + 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, bool blockingLock) { + const int WAIT_TIME = 10; // wait up to 10ms in the try lock case + SharedNodePointer node; + // if caller wants us to block and guarantee the correct answer, then honor that request + if (blockingLock) { + // this will block till we can get access + QMutexLocker locker(&_nodeHashMutex); + node = _nodeHash.value(nodeUUID); + } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block + node = _nodeHash.value(nodeUUID); + _nodeHashMutex.unlock(); + } + return node; + } + +SharedNodePointer LimitedNodeList::sendingNodeForPacket(const QByteArray& packet) { + QUuid nodeUUID = uuidFromPacketHeader(packet); + + // return the matching node, or NULL if there is no match + return nodeWithUUID(nodeUUID); +} + +NodeHash LimitedNodeList::getNodeHash() { + QMutexLocker locker(&_nodeHashMutex); + return NodeHash(_nodeHash); +} + +void LimitedNodeList::eraseAllNodes() { + qDebug() << "Clearing the NodeList. Deleting all nodes in list."; + + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + // iterate the nodes in the list + while (nodeItem != _nodeHash.end()) { + nodeItem = killNodeAtHashIterator(nodeItem); + } +} + +void LimitedNodeList::reset() { + eraseAllNodes(); +} + +void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); + if (nodeItemToKill != _nodeHash.end()) { + killNodeAtHashIterator(nodeItemToKill); + } +} + +NodeHash::iterator LimitedNodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { + qDebug() << "Killed" << *nodeItemToKill.value(); + emit nodeKilled(nodeItemToKill.value()); + return _nodeHash.erase(nodeItemToKill); +} + +void LimitedNodeList::processKillNode(const QByteArray& dataByteArray) { + // read the node id + QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); + + // kill the node with this UUID, if it exists + killNodeWithUUID(nodeUUID); +} + +SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + _nodeHashMutex.lock(); + + if (!_nodeHash.contains(uuid)) { + + // we didn't have this node, so add them + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); + SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); + + _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); + + _nodeHashMutex.unlock(); + + qDebug() << "Added" << *newNode; + + emit nodeAdded(newNodeSharedPointer); + + return newNodeSharedPointer; + } else { + _nodeHashMutex.unlock(); + + return updateSocketsForNode(uuid, publicSocket, localSocket); + } +} + +SharedNodePointer LimitedNodeList::updateSocketsForNode(const QUuid& uuid, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + + SharedNodePointer matchingNode = nodeWithUUID(uuid); + + if (matchingNode) { + // perform appropriate updates to this node + QMutexLocker locker(&matchingNode->getMutex()); + + // check if we need to change this node's public or local sockets + if (publicSocket != matchingNode->getPublicSocket()) { + matchingNode->setPublicSocket(publicSocket); + qDebug() << "Public socket change for node" << *matchingNode; + } + + if (localSocket != matchingNode->getLocalSocket()) { + matchingNode->setLocalSocket(localSocket); + qDebug() << "Local socket change for node" << *matchingNode; + } + } + + return matchingNode; +} + +unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { + unsigned n = 0; + + foreach (const SharedNodePointer& node, getNodeHash()) { + // only send to the NodeTypes we are asked to send to. + if (destinationNodeTypes.contains(node->getType())) { + writeDatagram(packet, node); + ++n; + } + } + + return n; +} + +SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { + + if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (node->getType() == nodeType) { + return node; + } + } + } + return SharedNodePointer(); +} + +void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { + packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); + bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); +} + +void LimitedNodeList::resetPacketStats() { + _numCollectedPackets = 0; + _numCollectedBytes = 0; + _packetStatTimer.restart(); +} + +void LimitedNodeList::removeSilentNodes() { + + _nodeHashMutex.lock(); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + while (nodeItem != _nodeHash.end()) { + SharedNodePointer node = nodeItem.value(); + + node->getMutex().lock(); + + if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > (NODE_SILENCE_THRESHOLD_MSECS * 1000)) { + // call our private method to kill this node (removes it and emits the right signal) + nodeItem = killNodeAtHashIterator(nodeItem); + } else { + // we didn't kill this node, push the iterator forwards + ++nodeItem; + } + + node->getMutex().unlock(); + } + + _nodeHashMutex.unlock(); +} diff --git a/libraries/shared/src/NodeList.h b/libraries/networking/src/LimitedNodeList.h similarity index 53% rename from libraries/shared/src/NodeList.h rename to libraries/networking/src/LimitedNodeList.h index 86c487e2d6..f941a8aa5d 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -1,6 +1,6 @@ // -// NodeList.h -// libraries/shared/src +// LimitedNodeList.h +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -9,14 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_NodeList_h -#define hifi_NodeList_h +#ifndef hifi_LimitedNodeList_h +#define hifi_LimitedNodeList_h -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif #include #include @@ -32,12 +27,14 @@ #include #include -#include "DomainInfo.h" +#include + +#include "DomainHandler.h" #include "Node.h" -const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000; -const quint64 DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; -const quint64 PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000; +const int MAX_PACKET_SIZE = 1500; + +const quint64 NODE_SILENCE_THRESHOLD_MSECS = 2 * 1000; extern const char SOLO_NODE_TYPES[2]; @@ -45,9 +42,6 @@ extern const QUrl DEFAULT_NODE_AUTH_URL; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; - -class Assignment; class HifiSockAddr; typedef QSet NodeSet; @@ -56,57 +50,31 @@ typedef QSharedPointer SharedNodePointer; typedef QHash NodeHash; Q_DECLARE_METATYPE(SharedNodePointer) -typedef quint8 PingType_t; -namespace PingType { - const PingType_t Agnostic = 0; - const PingType_t Local = 1; - const PingType_t Public = 2; - const PingType_t Symmetric = 3; -} - -class NodeList : public QObject { +class LimitedNodeList : public QObject { Q_OBJECT public: - static NodeList* createInstance(char ownerType, unsigned short int socketListenPort = 0); - static NodeList* getInstance(); - NodeType_t getOwnerType() const { return _ownerType; } - void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + static LimitedNodeList* createInstance(unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static LimitedNodeList* getInstance(); const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - + QUdpSocket& getNodeSocket() { return _nodeSocket; } + QUdpSocket& getDTLSSocket(); bool packetVersionAndHashMatch(const QByteArray& packet); qint64 writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + qint64 writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr); qint64 writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); - qint64 sendStatsToDomainServer(const QJsonObject& statsObject); void(*linkedDataCreateCallback)(Node *); NodeHash getNodeHash(); int size() const { return _nodeHash.size(); } - int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } - DomainInfo& getDomainInfo() { return _domainInfo; } - - const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } - void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); - void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); - void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } - - int processDomainServerList(const QByteArray& packet); - - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } - void sendAssignment(Assignment& assignment); - - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); - QByteArray constructPingReplyPacket(const QByteArray& pingPacket); - void pingPunchForInactiveNode(const SharedNodePointer& node); - SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true); SharedNodePointer sendingNodeForPacket(const QByteArray& packet); @@ -126,15 +94,10 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); - - void loadData(QSettings* settings); - void saveData(QSettings* settings); public slots: void reset(); void eraseAllNodes(); - void sendDomainServerCheckIn(); - void pingInactiveNodes(); void removeSilentNodes(); void killNodeWithUUID(const QUuid& nodeUUID); @@ -142,45 +105,29 @@ signals: void uuidChanged(const QUuid& ownerUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); - void limitOfSilentDomainCheckInsReached(); -private slots: - void domainServerAuthReply(const QJsonObject& jsonObject); -private: - static NodeList* _sharedInstance; +protected: + static LimitedNodeList* _sharedInstance; - NodeList(char ownerType, unsigned short int socketListenPort); - 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(); - void processSTUNResponse(const QByteArray& packet); + LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort); + 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, const QUuid& connectionSecret); NodeHash::iterator killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill); - void processDomainServerAuthRequest(const QByteArray& packet); - void requestAuthForDomainServer(); - void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); - void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); void changeSendSocketBufferSize(int numSendBytes); + QUuid _sessionUUID; NodeHash _nodeHash; QMutex _nodeHashMutex; QUdpSocket _nodeSocket; - NodeType_t _ownerType; - NodeSet _nodeTypesOfInterest; - DomainInfo _domainInfo; - QUuid _sessionUUID; - int _numNoReplyDomainCheckIns; - HifiSockAddr _assignmentServerSocket; - HifiSockAddr _publicSockAddr; - bool _hasCompletedInitialSTUNFailure; - unsigned int _stunRequestsSinceSuccess; + QUdpSocket* _dtlsSocket; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; }; -#endif // hifi_NodeList_h +#endif // hifi_LimitedNodeList_h diff --git a/libraries/shared/src/Logging.cpp b/libraries/networking/src/Logging.cpp similarity index 99% rename from libraries/shared/src/Logging.cpp rename to libraries/networking/src/Logging.cpp index 2625cc744a..17a4706de0 100644 --- a/libraries/shared/src/Logging.cpp +++ b/libraries/networking/src/Logging.cpp @@ -1,6 +1,6 @@ // // Logging.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Logging.h b/libraries/networking/src/Logging.h similarity index 94% rename from libraries/shared/src/Logging.h rename to libraries/networking/src/Logging.h index edbe8f62ee..c52812bd33 100644 --- a/libraries/shared/src/Logging.h +++ b/libraries/networking/src/Logging.h @@ -1,6 +1,6 @@ // // Logging.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. // Copyright 2013 High Fidelity, Inc. @@ -12,12 +12,6 @@ #ifndef hifi_Logging_h #define hifi_Logging_h -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif - #include const int LOGSTASH_UDP_PORT = 9500; diff --git a/libraries/shared/src/NetworkPacket.cpp b/libraries/networking/src/NetworkPacket.cpp similarity index 98% rename from libraries/shared/src/NetworkPacket.cpp rename to libraries/networking/src/NetworkPacket.cpp index b948b10c96..bf18aa9b37 100644 --- a/libraries/shared/src/NetworkPacket.cpp +++ b/libraries/networking/src/NetworkPacket.cpp @@ -1,6 +1,6 @@ // // NetworkPacket.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/NetworkPacket.h b/libraries/networking/src/NetworkPacket.h similarity index 98% rename from libraries/shared/src/NetworkPacket.h rename to libraries/networking/src/NetworkPacket.h index 6c086e2f5a..94ddf8d56e 100644 --- a/libraries/shared/src/NetworkPacket.h +++ b/libraries/networking/src/NetworkPacket.h @@ -1,6 +1,6 @@ // // NetworkPacket.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/Node.cpp b/libraries/networking/src/Node.cpp similarity index 96% rename from libraries/shared/src/Node.cpp rename to libraries/networking/src/Node.cpp index 13f700ad5a..9e6ea0dfec 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -1,6 +1,6 @@ // // Node.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -12,12 +12,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include // not available on windows, apparently not needed on mac -#endif - #include "Node.h" #include "SharedUtil.h" diff --git a/libraries/shared/src/Node.h b/libraries/networking/src/Node.h similarity index 97% rename from libraries/shared/src/Node.h rename to libraries/networking/src/Node.h index e7ebbcfb13..3b3237cf6e 100644 --- a/libraries/shared/src/Node.h +++ b/libraries/networking/src/Node.h @@ -1,6 +1,6 @@ // // Node.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. // Copyright 2013 High Fidelity, Inc. @@ -15,12 +15,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif - #include #include #include diff --git a/libraries/shared/src/NodeData.cpp b/libraries/networking/src/NodeData.cpp similarity index 92% rename from libraries/shared/src/NodeData.cpp rename to libraries/networking/src/NodeData.cpp index 6b71ef8f37..2e6fa9e8e1 100644 --- a/libraries/shared/src/NodeData.cpp +++ b/libraries/networking/src/NodeData.cpp @@ -1,6 +1,6 @@ // // NodeData.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/19/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/NodeData.h b/libraries/networking/src/NodeData.h similarity index 95% rename from libraries/shared/src/NodeData.h rename to libraries/networking/src/NodeData.h index 3b26d5b6d5..b865e444a9 100644 --- a/libraries/shared/src/NodeData.h +++ b/libraries/networking/src/NodeData.h @@ -1,6 +1,6 @@ // // NodeData.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/19/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp new file mode 100644 index 0000000000..93377c7763 --- /dev/null +++ b/libraries/networking/src/NodeList.cpp @@ -0,0 +1,590 @@ +// +// NodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include + +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "NodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +NodeList* NodeList::_sharedInstance = NULL; + +NodeList* NodeList::createInstance(char ownerType, unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new NodeList(ownerType, socketListenPort, dtlsPort); + LimitedNodeList::_sharedInstance = _sharedInstance; + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("NodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +NodeList* NodeList::getInstance() { + if (!_sharedInstance) { + qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + +NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : + LimitedNodeList(socketListenPort, dtlsListenPort), + _ownerType(newOwnerType), + _nodeTypesOfInterest(), + _domainHandler(this), + _numNoReplyDomainCheckIns(0), + _assignmentServerSocket(), + _publicSockAddr(), + _hasCompletedInitialSTUNFailure(false), + _stunRequestsSinceSuccess(0) +{ + // clear our NodeList when the domain changes + connect(&_domainHandler, &DomainHandler::hostnameChanged, this, &NodeList::reset); + + // clear our NodeList when logout is requested + connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + // perform a function when DTLS handshake is completed + connect(&_domainHandler, &DomainHandler::completedDTLSHandshake, this, &NodeList::completedDTLSHandshake); +} + +qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { + QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); + QDataStream statsPacketStream(&statsPacket, QIODevice::Append); + + statsPacketStream << statsObject.toVariantMap(); + + return writeDatagram(statsPacket, _domainHandler.getSockAddr(), QUuid()); +} + +void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + quint64 ourOriginalTime, othersReplyTime; + + packetStream >> pingType >> ourOriginalTime >> othersReplyTime; + + quint64 now = usecTimestampNow(); + int pingTime = now - ourOriginalTime; + int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight + + // The other node's expected time should be our original time plus the one way flight time + // anything other than that is clock skew + quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; + int clockSkew = othersReplyTime - othersExprectedReply; + + sendingNode->setPingMs(pingTime / 1000); + sendingNode->setClockSkewUsec(clockSkew); + + const bool wantDebug = false; + + if (wantDebug) { + qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << + " now: " << now << "\n" << + " ourTime: " << ourOriginalTime << "\n" << + " pingTime: " << pingTime << "\n" << + " oneWayFlightTime: " << oneWayFlightTime << "\n" << + " othersReplyTime: " << othersReplyTime << "\n" << + " othersExprectedReply: " << othersExprectedReply << "\n" << + " clockSkew: " << clockSkew; + } +} + +void NodeList::completedDTLSHandshake() { + // at this point, we've got a DTLS socket + // make this NodeList the handler of DTLS packets + connect(_dtlsSocket, &QUdpSocket::readyRead, this, &NodeList::processAvailableDTLSDatagrams); +} + +void NodeList::processAvailableDTLSDatagrams() { + while (_dtlsSocket->hasPendingDatagrams()) { + QByteArray dtlsPacket(_dtlsSocket->pendingDatagramSize(), 0); + + // pull the data from this user off the stack and process it + int receivedBytes = gnutls_record_recv(*_domainHandler.getDTLSSession()->getGnuTLSSession(), + dtlsPacket.data(), dtlsPacket.size()); + if (receivedBytes > 0) { + // successful data receive, hand this off to processNodeData + processNodeData(_domainHandler.getSockAddr(), dtlsPacket.left(receivedBytes)); + } else if (gnutls_error_is_fatal(receivedBytes)) { + qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- receiving DTLS packet from domain-server."; + } else { + qDebug() << "non fatal receive" << receivedBytes; + } + } +} + +void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + switch (packetTypeForPacket(packet)) { + case PacketTypeDomainList: { + processDomainServerList(packet); + break; + } + case PacketTypeDomainServerRequireDTLS: { + _domainHandler.parseDTLSRequirementPacket(packet); + break; + } + case PacketTypePing: { + // send back a reply + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + QByteArray replyPacket = constructPingReplyPacket(packet); + writeDatagram(replyPacket, matchingNode, senderSockAddr); + + // If we don't have a symmetric socket for this node and this socket doesn't match + // what we have for public and local then set it as the symmetric. + // This allows a server on a reachable port to communicate with nodes on symmetric NATs + if (matchingNode->getSymmetricSocket().isNull()) { + if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { + matchingNode->setSymmetricSocket(senderSockAddr); + } + } + } + + break; + } + case PacketTypePingReply: { + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + + if (sendingNode) { + sendingNode->setLastHeardMicrostamp(usecTimestampNow()); + + // activate the appropriate socket for this node, if not yet updated + activateSocketFromNodeCommunication(packet, sendingNode); + + // set the ping time for this node for stat collection + timePingReply(packet, sendingNode); + } + + break; + } + case PacketTypeStunResponse: { + // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch + // pass it along so it can be processed into our public address and port + processSTUNResponse(packet); + break; + } + default: + LimitedNodeList::processNodeData(senderSockAddr, packet); + break; + } +} + +void NodeList::reset() { + LimitedNodeList::reset(); + + _numNoReplyDomainCheckIns = 0; + + // refresh the owner UUID to the NULL UUID + setSessionUUID(QUuid()); + + // clear the domain connection information + _domainHandler.clearConnectionInfo(); + + // also disconnect from the DTLS socket readyRead() so it can handle handshaking + disconnect(_dtlsSocket, 0, this, 0); +} + +void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { + _nodeTypesOfInterest << nodeTypeToAdd; +} + +void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { + _nodeTypesOfInterest.unite(setOfNodeTypes); +} + +const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; +const int NUM_BYTES_STUN_HEADER = 20; +const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; + +void NodeList::sendSTUNRequest() { + const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; + const unsigned short STUN_SERVER_PORT = 3478; + + 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); + + // lookup the IP for the STUN server + static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + + if (!_hasCompletedInitialSTUNFailure) { + qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); + } + + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), + stunSockAddr.getAddress(), stunSockAddr.getPort()); + + _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 + qDebug("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()); + } +} + +void NodeList::processSTUNResponse(const QByteArray& packet) { + // check the cookie to make sure this is actually a STUN response + // 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; + + // reset the number of failed STUN requests since last success + _stunRequestsSinceSuccess = 0; + + int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; + + uint8_t addressFamily = 0; + memcpy(&addressFamily, packet.data(), 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); + + qDebug("New public socket received from STUN server is %s:%hu", + _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), + _publicSockAddr.getPort()); + + } + + _hasCompletedInitialSTUNFailure = true; + + break; + } + } 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; + } + } + } +} + +void NodeList::sendDomainServerCheckIn() { + if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { + // we don't know our public socket and we need to send it to the domain server + // send a STUN request to figure it out + sendSTUNRequest(); + } else if (!_domainHandler.getIP().isNull()) { + + DTLSClientSession* dtlsSession = _domainHandler.getDTLSSession(); + bool isUsingDTLS = false; + + if (dtlsSession) { + if (!dtlsSession->completedHandshake()) { + // if the handshake process is not complete then we can't check in, so return + return; + } else { + isUsingDTLS = true; + } + } + + // construct the DS check in packet + QUuid packetUUID = (!_sessionUUID.isNull() ? _sessionUUID : _domainHandler.getAssignmentUUID()); + + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest, packetUUID); + QDataStream packetStream(&domainServerPacket, QIODevice::Append); + + // pack our data to send to the domain-server + packetStream << _ownerType << _publicSockAddr + << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) + << (quint8) _nodeTypesOfInterest.size(); + + // copy over the bytes for node types of interest, if required + foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { + packetStream << nodeTypeOfInterest; + } + + if (!isUsingDTLS) { + writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); + } else { + dtlsSession->writeDatagram(domainServerPacket); + } + + + 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 indicates that + emit limitOfSilentDomainCheckInsReached(); + } + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; + } +} + +int NodeList::processDomainServerList(const QByteArray& packet) { + // this is a packet from the domain server, reset the count of un-replied check-ins + _numNoReplyDomainCheckIns = 0; + + // if this was the first domain-server list from this domain, we've now connected + _domainHandler.setIsConnected(true); + + int readNodes = 0; + + // setup variables to read into from QDataStream + qint8 nodeType; + + QUuid nodeUUID, connectionUUID; + + HifiSockAddr nodePublicSocket; + HifiSockAddr nodeLocalSocket; + + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + // pull our owner UUID from the packet, it's always the first thing + QUuid newUUID; + packetStream >> newUUID; + setSessionUUID(newUUID); + + // pull each node in the packet + while(packetStream.device()->pos() < packet.size()) { + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; + + // 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); + + packetStream >> connectionUUID; + node->setConnectionSecret(connectionUUID); + } + + // 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::sendAssignment(Assignment& assignment) { + + PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand + ? PacketTypeCreateAssignment + : PacketTypeRequestAssignment; + + QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); + QDataStream packetStream(&packet, QIODevice::Append); + + packetStream << assignment; + + static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); + + const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() + ? &DEFAULT_ASSIGNMENT_SOCKET + : &_assignmentServerSocket; + + _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); +} + +QByteArray NodeList::constructPingPacket(PingType_t pingType) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); + + QDataStream packetStream(&pingPacket, QIODevice::Append); + + packetStream << pingType; + packetStream << usecTimestampNow(); + + return pingPacket; +} + +QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { + QDataStream pingPacketStream(pingPacket); + pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + + PingType_t typeFromOriginalPing; + pingPacketStream >> typeFromOriginalPing; + + quint64 timeFromOriginalPing; + pingPacketStream >> timeFromOriginalPing; + + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); + QDataStream packetStream(&replyPacket, QIODevice::Append); + + packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); + + return replyPacket; +} + +void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = constructPingPacket(PingType::Local); + writeDatagram(localPingPacket, node, node->getLocalSocket()); + + QByteArray publicPingPacket = constructPingPacket(PingType::Public); + writeDatagram(publicPingPacket, node, node->getPublicSocket()); + + if (!node->getSymmetricSocket().isNull()) { + QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); + writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); + } +} + +void NodeList::pingInactiveNodes() { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (!node->getActiveSocket()) { + // we don't have an active link to this node, ping it to set that up + pingPunchForInactiveNode(node); + } + } +} + +void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { + // deconstruct this ping packet to see if it is a public or local reply + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + packetStream >> pingType; + + // if this is a local or public ping then we can activate a socket + // we do nothing with agnostic pings, those are simply for timing + if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { + sendingNode->activateLocalSocket(); + } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { + sendingNode->activatePublicSocket(); + } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { + sendingNode->activateSymmetricSocket(); + } +} + +const QString QSETTINGS_GROUP_NAME = "NodeList"; +const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; + +void NodeList::loadData(QSettings *settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); + + if (domainServerHostname.size() > 0) { + _domainHandler.setHostname(domainServerHostname); + } else { + _domainHandler.setHostname(DEFAULT_DOMAIN_HOSTNAME); + } + + settings->endGroup(); +} + +void NodeList::saveData(QSettings* settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + if (_domainHandler.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { + // the user is using a different hostname, store it + settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHandler.getHostname())); + } else { + // the user has switched back to default, remove the current setting + settings->remove(DOMAIN_SERVER_SETTING_KEY); + } + + settings->endGroup(); +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h new file mode 100644 index 0000000000..c55f08e7f0 --- /dev/null +++ b/libraries/networking/src/NodeList.h @@ -0,0 +1,114 @@ +// +// NodeList.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 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_NodeList_h +#define hifi_NodeList_h + +#include +#include + +#ifndef _WIN32 +#include // not on windows, not needed for mac or windows +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DomainHandler.h" +#include "LimitedNodeList.h" +#include "Node.h" + +const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; + +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; + +class Assignment; + +typedef quint8 PingType_t; +namespace PingType { + const PingType_t Agnostic = 0; + const PingType_t Local = 1; + const PingType_t Public = 2; + const PingType_t Symmetric = 3; +} + +class NodeList : public LimitedNodeList { + Q_OBJECT +public: + static NodeList* createInstance(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static NodeList* getInstance(); + NodeType_t getOwnerType() const { return _ownerType; } + void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + + qint64 sendStatsToDomainServer(const QJsonObject& statsObject); + + int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } + DomainHandler& getDomainHandler() { return _domainHandler; } + + const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } + void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); + void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); + void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } + + void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + + int processDomainServerList(const QByteArray& packet); + + void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } + void sendAssignment(Assignment& assignment); + + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); + QByteArray constructPingReplyPacket(const QByteArray& pingPacket); + + void pingPunchForInactiveNode(const SharedNodePointer& node); + + void loadData(QSettings* settings); + void saveData(QSettings* settings); +public slots: + void reset(); + void sendDomainServerCheckIn(); + void pingInactiveNodes(); + void completedDTLSHandshake(); + void processAvailableDTLSDatagrams(); +signals: + void limitOfSilentDomainCheckInsReached(); +private: + static NodeList* _sharedInstance; + + NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort); + 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(); + void processSTUNResponse(const QByteArray& packet); + + void processDomainServerAuthRequest(const QByteArray& packet); + void requestAuthForDomainServer(); + void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); + void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); + + NodeType_t _ownerType; + NodeSet _nodeTypesOfInterest; + DomainHandler _domainHandler; + int _numNoReplyDomainCheckIns; + HifiSockAddr _assignmentServerSocket; + HifiSockAddr _publicSockAddr; + bool _hasCompletedInitialSTUNFailure; + unsigned int _stunRequestsSinceSuccess; +}; + +#endif // hifi_NodeList_h diff --git a/libraries/shared/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp similarity index 98% rename from libraries/shared/src/OAuthAccessToken.cpp rename to libraries/networking/src/OAuthAccessToken.cpp index ab5ec6462a..365b07a935 100644 --- a/libraries/shared/src/OAuthAccessToken.cpp +++ b/libraries/networking/src/OAuthAccessToken.cpp @@ -1,6 +1,6 @@ // // OAuthAccessToken.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h similarity index 97% rename from libraries/shared/src/OAuthAccessToken.h rename to libraries/networking/src/OAuthAccessToken.h index 7f7c621231..36859b79f8 100644 --- a/libraries/shared/src/OAuthAccessToken.h +++ b/libraries/networking/src/OAuthAccessToken.h @@ -1,6 +1,6 @@ // // OAuthAccessToken.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp similarity index 81% rename from libraries/shared/src/PacketHeaders.cpp rename to libraries/networking/src/PacketHeaders.cpp index 2832fde56e..fdbdca3db7 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -1,6 +1,6 @@ // // PacketHeaders.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 6/28/13. // Copyright 2013 High Fidelity, Inc. @@ -55,15 +55,10 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeDomainList: case PacketTypeDomainListRequest: - return 1; + return 2; case PacketTypeCreateAssignment: case PacketTypeRequestAssignment: - return 1; - case PacketTypeDataServerGet: - case PacketTypeDataServerPut: - case PacketTypeDataServerConfirm: - case PacketTypeDataServerSend: - return 1; + return 2; case PacketTypeVoxelSet: case PacketTypeVoxelSetDestructive: return 1; @@ -92,15 +87,17 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU char* position = packet + numTypeBytes + sizeof(PacketVersion); - QUuid packUUID = connectionUUID.isNull() ? NodeList::getInstance()->getSessionUUID() : connectionUUID; + QUuid packUUID = connectionUUID.isNull() ? LimitedNodeList::getInstance()->getSessionUUID() : connectionUUID; QByteArray rfcUUID = packUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); position += NUM_BYTES_RFC4122_UUID; - // pack 16 bytes of zeros where the md5 hash will be placed one data is packed - memset(position, 0, NUM_BYTES_MD5_HASH); - position += NUM_BYTES_MD5_HASH; + if (!NON_VERIFIED_PACKETS.contains(type)) { + // pack 16 bytes of zeros where the md5 hash will be placed one data is packed + memset(position, 0, NUM_BYTES_MD5_HASH); + position += NUM_BYTES_MD5_HASH; + } // return the number of bytes written for pointer pushing return position - packet; @@ -108,16 +105,26 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU int numBytesForPacketHeader(const QByteArray& packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet.data()) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet.data()) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeader(const char* packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeaderGivenPacketType(PacketType type) { - return (int) ceilf((float)type / 255) + NUM_STATIC_HEADER_BYTES; + return (int) ceilf((float)type / 255) + + numHashBytesInPacketHeaderGivenPacketType(type) + + NUM_STATIC_HEADER_BYTES; +} + +int numHashBytesInPacketHeaderGivenPacketType(PacketType type) { + return (NON_VERIFIED_PACKETS.contains(type) ? 0 : NUM_BYTES_MD5_HASH); } QUuid uuidFromPacketHeader(const QByteArray& packet) { diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h similarity index 78% rename from libraries/shared/src/PacketHeaders.h rename to libraries/networking/src/PacketHeaders.h index 3183e8f12d..3e2ff3d8b0 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -1,6 +1,6 @@ // // PacketHeaders.h -// libraries/shared/src +// libraries/networking/src // // Created by Stephen Birarda on 4/8/13. // Copyright 2013 High Fidelity, Inc. @@ -13,6 +13,7 @@ #define hifi_PacketHeaders_h #include +#include #include #include "UUID.h" @@ -37,9 +38,9 @@ enum PacketType { PacketTypeDomainListRequest, PacketTypeRequestAssignment, PacketTypeCreateAssignment, - PacketTypeDataServerPut, - PacketTypeDataServerGet, - PacketTypeDataServerSend, + PacketTypeDataServerPut, // reusable + PacketTypeDataServerGet, // reusable + PacketTypeDataServerSend, // reusable PacketTypeDataServerConfirm, PacketTypeVoxelQuery, PacketTypeVoxelData, @@ -57,16 +58,21 @@ enum PacketType { PacketTypeMetavoxelData, PacketTypeAvatarIdentity, PacketTypeAvatarBillboard, - PacketTypeDomainConnectRequest, - PacketTypeDomainServerAuthRequest, - PacketTypeNodeJsonStats + PacketTypeDomainConnectRequest, // reusable + PacketTypeDomainServerRequireDTLS, + PacketTypeNodeJsonStats, }; typedef char PacketVersion; +const QSet NON_VERIFIED_PACKETS = QSet() + << PacketTypeDomainServerRequireDTLS << PacketTypeDomainList << PacketTypeDomainListRequest + << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse + << PacketTypeNodeJsonStats; + const int NUM_BYTES_MD5_HASH = 16; -const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; -const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_STATIC_HEADER_BYTES; +const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; +const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_BYTES_MD5_HASH + NUM_STATIC_HEADER_BYTES; PacketVersion versionForPacketType(PacketType type); @@ -76,6 +82,8 @@ QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connection int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID); +int numHashBytesInPacketHeaderGivenPacketType(PacketType type); + int numBytesForPacketHeader(const QByteArray& packet); int numBytesForPacketHeader(const char* packet); int numBytesForPacketHeaderGivenPacketType(PacketType type); diff --git a/libraries/shared/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp similarity index 99% rename from libraries/shared/src/PacketSender.cpp rename to libraries/networking/src/PacketSender.cpp index 489b083d92..5f7502a738 100644 --- a/libraries/shared/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -1,6 +1,6 @@ // // PacketSender.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/PacketSender.h b/libraries/networking/src/PacketSender.h similarity index 99% rename from libraries/shared/src/PacketSender.h rename to libraries/networking/src/PacketSender.h index cc65564461..7d2c0dc8aa 100644 --- a/libraries/shared/src/PacketSender.h +++ b/libraries/networking/src/PacketSender.h @@ -1,6 +1,6 @@ // // PacketSender.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp similarity index 98% rename from libraries/shared/src/ReceivedPacketProcessor.cpp rename to libraries/networking/src/ReceivedPacketProcessor.cpp index 8394559524..d54e165285 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -1,6 +1,6 @@ // // ReceivedPacketProcessor.cpp -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h similarity index 98% rename from libraries/shared/src/ReceivedPacketProcessor.h rename to libraries/networking/src/ReceivedPacketProcessor.h index a34b43626c..f8306b4896 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -1,6 +1,6 @@ // // ReceivedPacketProcessor.h -// libraries/shared/src +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp similarity index 95% rename from libraries/shared/src/ThreadedAssignment.cpp rename to libraries/networking/src/ThreadedAssignment.cpp index 1bd79895ab..4b92f8ba38 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -44,11 +44,11 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeRemovalTimer = new QTimer(this); connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_MSECS); if (shouldSendStats) { // send a stats packet every 1 second diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h similarity index 100% rename from libraries/shared/src/ThreadedAssignment.h rename to libraries/networking/src/ThreadedAssignment.h diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index 422089a57d..88554ed5f8 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -22,9 +22,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt index b483e3e479..26d2b7fc26 100644 --- a/libraries/particles/CMakeLists.txt +++ b/libraries/particles/CMakeLists.txt @@ -23,9 +23,16 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 615000e260..20569e2fe0 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -28,6 +28,12 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) \ No newline at end of file diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 3af7272cc1..7f9a34492d 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -32,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) \ No newline at end of file diff --git a/libraries/shared/src/DomainInfo.cpp b/libraries/shared/src/DomainInfo.cpp deleted file mode 100644 index 34498f4c50..0000000000 --- a/libraries/shared/src/DomainInfo.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// -// DomainInfo.cpp -// libraries/shared/src -// -// Created by Stephen Birarda on 2/18/2014. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include "AccountManager.h" - -#include "DomainInfo.h" - -DomainInfo::DomainInfo() : - _uuid(), - _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), - _assignmentUUID(), - _connectionSecret(), - _registrationToken(), - _rootAuthenticationURL(), - _publicKey(), - _isConnected(false) -{ - // clear appropriate variables after a domain-server logout - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete, this, &DomainInfo::logout); -} - -void DomainInfo::clearConnectionInfo() { - _uuid = QUuid(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _rootAuthenticationURL = QUrl(); - _publicKey = QString(); - _isConnected = false; -} - -void DomainInfo::reset() { - clearConnectionInfo(); - _hostname = QString(); - _sockAddr.setAddress(QHostAddress::Null); -} - -void DomainInfo::parseAuthInformationFromJsonObject(const QJsonObject& jsonObject) { - QJsonObject dataObject = jsonObject["data"].toObject(); - _connectionSecret = QUuid(dataObject["connection_secret"].toString()); - _registrationToken = QByteArray::fromHex(dataObject["registration_token"].toString().toUtf8()); - _publicKey = dataObject["public_key"].toString(); -} - -void DomainInfo::setSockAddr(const HifiSockAddr& sockAddr) { - if (_sockAddr != sockAddr) { - // we should reset on a sockAddr change - reset(); - // change the sockAddr - _sockAddr = sockAddr; - } -} - -void DomainInfo::setHostname(const QString& hostname) { - - if (hostname != _hostname) { - // re-set the domain info so that auth information is reloaded - reset(); - - int colonIndex = hostname.indexOf(':'); - - if (colonIndex > 0) { - // the user has included a custom DS port with the hostname - - // the new hostname is everything up to the colon - _hostname = hostname.left(colonIndex); - - // grab the port by reading the string after the colon - _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); - - qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); - - } else { - // no port included with the hostname, simply set the member variable and reset the domain server port to default - _hostname = hostname; - _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); - } - - // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname - qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); - QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); - - emit hostnameChanged(_hostname); - } -} - -void DomainInfo::completedHostnameLookup(const QHostInfo& hostInfo) { - for (int i = 0; i < hostInfo.addresses().size(); i++) { - if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { - _sockAddr.setAddress(hostInfo.addresses()[i]); - qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), - _sockAddr.getAddress().toString().toLocal8Bit().constData()); - return; - } - } - - // if we got here then we failed to lookup the address - qDebug("Failed domain server lookup"); -} - -void DomainInfo::setIsConnected(bool isConnected) { - if (_isConnected != isConnected) { - _isConnected = isConnected; - - if (_isConnected) { - emit connectedToDomain(_hostname); - } - } -} - -void DomainInfo::logout() { - // clear any information related to auth for this domain, assuming it had requested auth - if (!_rootAuthenticationURL.isEmpty()) { - _rootAuthenticationURL = QUrl(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _publicKey = QString(); - _isConnected = false; - } -} diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp new file mode 100644 index 0000000000..d20f4276f1 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -0,0 +1,98 @@ +// +// HifiConfigVariantMap.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include + +#include "HifiConfigVariantMap.h" + +QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { + + QVariantMap mergedMap; + + // Add anything in the CL parameter list to the variant map. + // Take anything with a dash in it as a key, and the values after it as the value. + + const QString DASHED_KEY_REGEX_STRING = "(^-{1,2})([\\w-]+)"; + QRegExp dashedKeyRegex(DASHED_KEY_REGEX_STRING); + + int keyIndex = argumentList.indexOf(dashedKeyRegex); + int nextKeyIndex = 0; + + // check if there is a config file to read where we can pull config info not passed on command line + const QString CONFIG_FILE_OPTION = "--config"; + + while (keyIndex != -1) { + if (argumentList[keyIndex] != CONFIG_FILE_OPTION) { + // we have a key - look forward to see how many values associate to it + QString key = dashedKeyRegex.cap(2); + + nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + + if (nextKeyIndex == keyIndex + 1) { + // there's no value associated with this option, it's a boolean + // so add it to the variant map with NULL as value + mergedMap.insertMulti(key, QVariant()); + } else { + int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex; + + // there's at least one value associated with the option + // pull the first value to start + QString value = argumentList[keyIndex + 1]; + + // for any extra values, append them, with a space, to the value string + for (int i = keyIndex + 2; i < maxIndex; i++) { + value += " " + argumentList[i]; + } + + // add the finalized value to the merged map + mergedMap.insert(key, value); + } + + keyIndex = nextKeyIndex; + } else { + keyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + } + } + + int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION); + + if (configIndex != -1) { + // we have a config file - try and read it + QString configFilePath = argumentList[configIndex + 1]; + QFile configFile(configFilePath); + + if (configFile.exists()) { + qDebug() << "Reading JSON config file at" << configFilePath; + configFile.open(QIODevice::ReadOnly); + + QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); + QJsonObject rootObject = configDocument.object(); + + // enumerate the keys of the configDocument object + foreach(const QString& key, rootObject.keys()) { + + if (!mergedMap.contains(key)) { + // no match in existing list, add it + mergedMap.insert(key, QVariant(rootObject[key])); + } + } + } else { + qDebug() << "Could not find JSON config file at" << configFilePath; + } + } + + return mergedMap; +} diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h new file mode 100644 index 0000000000..378aa749c5 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -0,0 +1,22 @@ +// +// HifiConfigVariantMap.h +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_HifiConfigVariantMap_h +#define hifi_HifiConfigVariantMap_h + +#include + +class HifiConfigVariantMap { +public: + static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); +}; + +#endif // hifi_HifiConfigVariantMap_h diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp deleted file mode 100644 index 7ca6202398..0000000000 --- a/libraries/shared/src/NodeList.cpp +++ /dev/null @@ -1,981 +0,0 @@ -// -// NodeList.cpp -// libraries/shared/src -// -// Created by Stephen Birarda on 2/15/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "AccountManager.h" -#include "Assignment.h" -#include "HifiSockAddr.h" -#include "Logging.h" -#include "NodeList.h" -#include "PacketHeaders.h" -#include "SharedUtil.h" -#include "UUID.h" - -const char SOLO_NODE_TYPES[2] = { - NodeType::AvatarMixer, - NodeType::AudioMixer -}; - -const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); - -NodeList* NodeList::_sharedInstance = NULL; - -NodeList* NodeList::createInstance(char ownerType, unsigned short int socketListenPort) { - if (!_sharedInstance) { - NodeType::init(); - - _sharedInstance = new NodeList(ownerType, socketListenPort); - - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType(); - } else { - qDebug("NodeList createInstance called with existing instance."); - } - - return _sharedInstance; -} - -NodeList* NodeList::getInstance() { - if (!_sharedInstance) { - qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); - } - - return _sharedInstance; -} - - -NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : - _nodeHash(), - _nodeHashMutex(QMutex::Recursive), - _nodeSocket(this), - _ownerType(newOwnerType), - _nodeTypesOfInterest(), - _sessionUUID(), - _numNoReplyDomainCheckIns(0), - _assignmentServerSocket(), - _publicSockAddr(), - _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0), - _numCollectedPackets(0), - _numCollectedBytes(0), - _packetStatTimer() -{ - _nodeSocket.bind(QHostAddress::AnyIPv4, newSocketListenPort); - qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); - - // clear our NodeList when the domain changes - connect(&_domainInfo, &DomainInfo::hostnameChanged, this, &NodeList::reset); - - // clear our NodeList when logout is requested - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); - - const int LARGER_SNDBUF_SIZE = 1048576; - changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); - - _packetStatTimer.start(); -} - -void NodeList::changeSendSocketBufferSize(int numSendBytes) { - // change the socket send buffer size to be 1MB - int oldBufferSize = 0; - -#ifdef Q_OS_WIN - int sizeOfInt = sizeof(oldBufferSize); -#else - unsigned int sizeOfInt = sizeof(oldBufferSize); -#endif - - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); - - setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), - sizeof(numSendBytes)); - - int newBufferSize = 0; - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); - - qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; -} - -bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) { - PacketType checkType = packetTypeForPacket(packet); - if (packet[1] != versionForPacketType(checkType) - && checkType != PacketTypeStunResponse) { - PacketType mismatchType = packetTypeForPacket(packet); - int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); - - static QMultiMap versionDebugSuppressMap; - - QUuid senderUUID = uuidFromPacketHeader(packet); - if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { - qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" - << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" - << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; - - versionDebugSuppressMap.insert(senderUUID, checkType); - } - - return false; - } - - const QSet NON_VERIFIED_PACKETS = QSet() - << PacketTypeDomainServerAuthRequest << PacketTypeDomainConnectRequest - << PacketTypeStunResponse << PacketTypeDataServerConfirm - << PacketTypeDataServerGet << PacketTypeDataServerPut << PacketTypeDataServerSend - << PacketTypeCreateAssignment << PacketTypeRequestAssignment; - - if (!NON_VERIFIED_PACKETS.contains(checkType)) { - // figure out which node this is from - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - if (sendingNode) { - // check if the md5 hash in the header matches the hash we would expect - if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { - return true; - } else { - qDebug() << "Packet hash mismatch on" << checkType << "- Sender" - << uuidFromPacketHeader(packet); - } - } else { - if (checkType == PacketTypeDomainList) { - - if (_domainInfo.getRootAuthenticationURL().isEmpty() && _domainInfo.getUUID().isNull()) { - // if this is a domain-server that doesn't require auth, - // pull the UUID from this packet and set it as our domain-server UUID - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - - // we also know this domain-server requires no authentication - // so set the account manager root URL to the default one - AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL); - } - - if (_domainInfo.getUUID() == uuidFromPacketHeader(packet)) { - if (hashForPacketAndConnectionUUID(packet, _domainInfo.getConnectionSecret()) == hashFromPacketHeader(packet)) { - // this is a packet from the domain-server (PacketTypeDomainServerListRequest) - // and the sender UUID matches the UUID we expect for the domain - return true; - } else { - // this is a packet from the domain-server but there is a hash mismatch - qDebug() << "Packet hash mismatch on" << checkType << "from domain-server at" << _domainInfo.getHostname(); - return false; - } - } - } - - qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" - << uuidFromPacketHeader(packet); - } - } else { - return true; - } - - return false; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, - const QUuid& connectionSecret) { - QByteArray datagramCopy = datagram; - - // setup the MD5 hash for source verification in the header - replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); - - // stat collection for packets - ++_numCollectedPackets; - _numCollectedBytes += datagram.size(); - - qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - - if (bytesWritten < 0) { - qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); - } - - return bytesWritten; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - if (destinationNode) { - // if we don't have an ovveriden address, assume they want to send to the node's active socket - const HifiSockAddr* destinationSockAddr = &overridenSockAddr; - if (overridenSockAddr.isNull()) { - if (destinationNode->getActiveSocket()) { - // use the node's active socket as the destination socket - destinationSockAddr = destinationNode->getActiveSocket(); - } else { - // we don't have a socket to send to, return 0 - return 0; - } - } - - writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); - } - - // didn't have a destinationNode to send to, return 0 - return 0; -} - -qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); -} - -qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { - QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); - QDataStream statsPacketStream(&statsPacket, QIODevice::Append); - - statsPacketStream << statsObject.toVariantMap(); - - return writeDatagram(statsPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); -} - -void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - quint64 ourOriginalTime, othersReplyTime; - - packetStream >> pingType >> ourOriginalTime >> othersReplyTime; - - quint64 now = usecTimestampNow(); - int pingTime = now - ourOriginalTime; - int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight - - // The other node's expected time should be our original time plus the one way flight time - // anything other than that is clock skew - quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; - int clockSkew = othersReplyTime - othersExprectedReply; - - sendingNode->setPingMs(pingTime / 1000); - sendingNode->setClockSkewUsec(clockSkew); - - const bool wantDebug = false; - - if (wantDebug) { - qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << - " now: " << now << "\n" << - " ourTime: " << ourOriginalTime << "\n" << - " pingTime: " << pingTime << "\n" << - " oneWayFlightTime: " << oneWayFlightTime << "\n" << - " othersReplyTime: " << othersReplyTime << "\n" << - " othersExprectedReply: " << othersExprectedReply << "\n" << - " clockSkew: " << clockSkew; - } -} - -void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - switch (packetTypeForPacket(packet)) { - case PacketTypeDomainList: { - processDomainServerList(packet); - break; - } - case PacketTypeDomainServerAuthRequest: { - // the domain-server has asked us to auth via a data-server - processDomainServerAuthRequest(packet); - - break; - } - case PacketTypePing: { - // send back a reply - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - QByteArray replyPacket = constructPingReplyPacket(packet); - writeDatagram(replyPacket, matchingNode, senderSockAddr); - - // If we don't have a symmetric socket for this node and this socket doesn't match - // what we have for public and local then set it as the symmetric. - // This allows a server on a reachable port to communicate with nodes on symmetric NATs - if (matchingNode->getSymmetricSocket().isNull()) { - if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { - matchingNode->setSymmetricSocket(senderSockAddr); - } - } - } - - break; - } - case PacketTypePingReply: { - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - - if (sendingNode) { - sendingNode->setLastHeardMicrostamp(usecTimestampNow()); - - // activate the appropriate socket for this node, if not yet updated - activateSocketFromNodeCommunication(packet, sendingNode); - - // set the ping time for this node for stat collection - timePingReply(packet, sendingNode); - } - - break; - } - case PacketTypeStunResponse: { - // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch - // pass it along so it can be processed into our public address and port - processSTUNResponse(packet); - break; - } - default: - // the node decided not to do anything with this packet - // if it comes from a known source we should keep that node alive - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - } - - break; - } -} - -int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { - QMutexLocker locker(&matchingNode->getMutex()); - - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - matchingNode->recordBytesReceived(packet.size()); - - if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { - linkedDataCreateCallback(matchingNode.data()); - } - - QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); - - return matchingNode->getLinkedData()->parseData(packet); -} - -int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - - if (matchingNode) { - 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 NodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { - const int WAIT_TIME = 10; // wait up to 10ms in the try lock case - SharedNodePointer node; - // if caller wants us to block and guarantee the correct answer, then honor that request - if (blockingLock) { - // this will block till we can get access - QMutexLocker locker(&_nodeHashMutex); - node = _nodeHash.value(nodeUUID); - } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block - node = _nodeHash.value(nodeUUID); - _nodeHashMutex.unlock(); - } - return node; - } - -SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) { - QUuid nodeUUID = uuidFromPacketHeader(packet); - - // return the matching node, or NULL if there is no match - return nodeWithUUID(nodeUUID); -} - -NodeHash NodeList::getNodeHash() { - QMutexLocker locker(&_nodeHashMutex); - return NodeHash(_nodeHash); -} - -void NodeList::eraseAllNodes() { - qDebug() << "Clearing the NodeList. Deleting all nodes in list."; - - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - // iterate the nodes in the list - while (nodeItem != _nodeHash.end()) { - nodeItem = killNodeAtHashIterator(nodeItem); - } -} - -void NodeList::reset() { - eraseAllNodes(); - _numNoReplyDomainCheckIns = 0; - - // refresh the owner UUID to the NULL UUID - setSessionUUID(QUuid()); - - // clear the domain connection information - _domainInfo.clearConnectionInfo(); -} - -void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { - _nodeTypesOfInterest << nodeTypeToAdd; -} - -void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { - _nodeTypesOfInterest.unite(setOfNodeTypes); -} - -const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; -const int NUM_BYTES_STUN_HEADER = 20; -const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; - -void NodeList::sendSTUNRequest() { - const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; - const unsigned short STUN_SERVER_PORT = 3478; - - 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); - - // lookup the IP for the STUN server - static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - if (!_hasCompletedInitialSTUNFailure) { - qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); - } - - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); - - _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 - qDebug("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()); - } -} - -void NodeList::processSTUNResponse(const QByteArray& packet) { - // check the cookie to make sure this is actually a STUN response - // 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; - - // reset the number of failed STUN requests since last success - _stunRequestsSinceSuccess = 0; - - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; - - uint8_t addressFamily = 0; - memcpy(&addressFamily, packet.data(), 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); - - qDebug("New public socket received from STUN server is %s:%hu", - _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), - _publicSockAddr.getPort()); - - } - - _hasCompletedInitialSTUNFailure = true; - - break; - } - } 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; - } - } - } -} - -void NodeList::killNodeWithUUID(const QUuid& nodeUUID) { - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); - if (nodeItemToKill != _nodeHash.end()) { - killNodeAtHashIterator(nodeItemToKill); - } -} - -NodeHash::iterator NodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { - qDebug() << "Killed" << *nodeItemToKill.value(); - emit nodeKilled(nodeItemToKill.value()); - return _nodeHash.erase(nodeItemToKill); -} - -void NodeList::processKillNode(const QByteArray& dataByteArray) { - // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); - - // kill the node with this UUID, if it exists - killNodeWithUUID(nodeUUID); -} - -void NodeList::sendDomainServerCheckIn() { - if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { - // we don't know our public socket and we need to send it to the domain server - // send a STUN request to figure it out - sendSTUNRequest(); - } else if (!_domainInfo.getIP().isNull()) { - if (_domainInfo.getRootAuthenticationURL().isEmpty() - || !_sessionUUID.isNull() - || !_domainInfo.getRegistrationToken().isEmpty() ) { - // construct the DS check in packet - - PacketType domainPacketType = _sessionUUID.isNull() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; - - QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest) - ? _sessionUUID : _domainInfo.getAssignmentUUID(); - - QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); - QDataStream packetStream(&domainServerPacket, QIODevice::Append); - - if (domainPacketType == PacketTypeDomainConnectRequest) { - // we may need a registration token to present to the domain-server - packetStream << (quint8) !_domainInfo.getRegistrationToken().isEmpty(); - - if (!_domainInfo.getRegistrationToken().isEmpty()) { - // if we have a registration token send that along in the request - packetStream << _domainInfo.getRegistrationToken(); - } - } - - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); - - // copy over the bytes for node types of interest, if required - foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { - packetStream << nodeTypeOfInterest; - } - - writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); - 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 indicates that - emit limitOfSilentDomainCheckInsReached(); - } - - // increment the count of un-replied check-ins - _numNoReplyDomainCheckIns++; - } else if (AccountManager::getInstance().hasValidAccessToken()) { - // we have an access token we can use for the authentication server the domain-server requested - // so ask that server to provide us with information to connect to the domain-server - requestAuthForDomainServer(); - } - } -} - -void NodeList::setSessionUUID(const QUuid& sessionUUID) { - QUuid oldUUID = _sessionUUID; - _sessionUUID = sessionUUID; - - if (sessionUUID != oldUUID) { - qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) - << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); - emit uuidChanged(sessionUUID); - } -} - -int NodeList::processDomainServerList(const QByteArray& packet) { - // this is a packet from the domain server, reset the count of un-replied check-ins - _numNoReplyDomainCheckIns = 0; - - // if this was the first domain-server list from this domain, we've now connected - _domainInfo.setIsConnected(true); - - int readNodes = 0; - - // setup variables to read into from QDataStream - qint8 nodeType; - - QUuid nodeUUID, connectionUUID; - - HifiSockAddr nodePublicSocket; - HifiSockAddr nodeLocalSocket; - - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - // pull our owner UUID from the packet, it's always the first thing - QUuid newUUID; - packetStream >> newUUID; - setSessionUUID(newUUID); - - // pull each node in the packet - while(packetStream.device()->pos() < packet.size()) { - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; - - // 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(_domainInfo.getIP()); - } - - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); - - packetStream >> connectionUUID; - node->setConnectionSecret(connectionUUID); - } - - // 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::domainServerAuthReply(const QJsonObject& jsonObject) { - _domainInfo.parseAuthInformationFromJsonObject(jsonObject); -} - -void NodeList::requestAuthForDomainServer() { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "domainServerAuthReply"; - - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/" - + uuidStringWithoutCurlyBraces(_domainInfo.getUUID()) + "/auth.json", - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void NodeList::processDomainServerAuthRequest(const QByteArray& packet) { - QDataStream authPacketStream(packet); - authPacketStream.skipRawData(numBytesForPacketHeader(packet)); - - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - AccountManager& accountManager = AccountManager::getInstance(); - - // grab the hostname this domain-server wants us to authenticate with - QUrl authenticationRootURL; - authPacketStream >> authenticationRootURL; - - accountManager.setAuthURL(authenticationRootURL); - _domainInfo.setRootAuthenticationURL(authenticationRootURL); - - if (AccountManager::getInstance().checkAndSignalForAccessToken()) { - // request a domain-server auth - requestAuthForDomainServer(); - } -} - -void NodeList::sendAssignment(Assignment& assignment) { - - PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand - ? PacketTypeCreateAssignment - : PacketTypeRequestAssignment; - - QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); - QDataStream packetStream(&packet, QIODevice::Append); - - packetStream << assignment; - - static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); - - const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() - ? &DEFAULT_ASSIGNMENT_SOCKET - : &_assignmentServerSocket; - - _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); -} - -QByteArray NodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); - - QDataStream packetStream(&pingPacket, QIODevice::Append); - - packetStream << pingType; - packetStream << usecTimestampNow(); - - return pingPacket; -} - -QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { - QDataStream pingPacketStream(pingPacket); - pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); - - PingType_t typeFromOriginalPing; - pingPacketStream >> typeFromOriginalPing; - - quint64 timeFromOriginalPing; - pingPacketStream >> timeFromOriginalPing; - - QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); - QDataStream packetStream(&replyPacket, QIODevice::Append); - - packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); - - return replyPacket; -} - -void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { - - // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = constructPingPacket(PingType::Local); - writeDatagram(localPingPacket, node, node->getLocalSocket()); - - QByteArray publicPingPacket = constructPingPacket(PingType::Public); - writeDatagram(publicPingPacket, node, node->getPublicSocket()); - - if (!node->getSymmetricSocket().isNull()) { - QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); - writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); - } -} - -SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - _nodeHashMutex.lock(); - - if (!_nodeHash.contains(uuid)) { - - // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); - SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); - - _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); - - _nodeHashMutex.unlock(); - - qDebug() << "Added" << *newNode; - - emit nodeAdded(newNodeSharedPointer); - - return newNodeSharedPointer; - } else { - _nodeHashMutex.unlock(); - - return updateSocketsForNode(uuid, publicSocket, localSocket); - } -} - -SharedNodePointer NodeList::updateSocketsForNode(const QUuid& uuid, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - - SharedNodePointer matchingNode = nodeWithUUID(uuid); - - if (matchingNode) { - // perform appropriate updates to this node - QMutexLocker locker(&matchingNode->getMutex()); - - // check if we need to change this node's public or local sockets - if (publicSocket != matchingNode->getPublicSocket()) { - matchingNode->setPublicSocket(publicSocket); - qDebug() << "Public socket change for node" << *matchingNode; - } - - if (localSocket != matchingNode->getLocalSocket()) { - matchingNode->setLocalSocket(localSocket); - qDebug() << "Local socket change for node" << *matchingNode; - } - } - - return matchingNode; -} - -unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { - unsigned n = 0; - - foreach (const SharedNodePointer& node, getNodeHash()) { - // only send to the NodeTypes we are asked to send to. - if (destinationNodeTypes.contains(node->getType())) { - writeDatagram(packet, node); - ++n; - } - } - - return n; -} - -void NodeList::pingInactiveNodes() { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (!node->getActiveSocket()) { - // we don't have an active link to this node, ping it to set that up - pingPunchForInactiveNode(node); - } - } -} - -void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { - // deconstruct this ping packet to see if it is a public or local reply - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - packetStream >> pingType; - - // if this is a local or public ping then we can activate a socket - // we do nothing with agnostic pings, those are simply for timing - if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { - sendingNode->activateLocalSocket(); - } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { - sendingNode->activatePublicSocket(); - } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { - sendingNode->activateSymmetricSocket(); - } -} - -SharedNodePointer NodeList::soloNodeOfType(char nodeType) { - - if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (node->getType() == nodeType) { - return node; - } - } - } - return SharedNodePointer(); -} - -void NodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { - packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); - bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); -} - -void NodeList::resetPacketStats() { - _numCollectedPackets = 0; - _numCollectedBytes = 0; - _packetStatTimer.restart(); -} - -void NodeList::removeSilentNodes() { - - _nodeHashMutex.lock(); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - while (nodeItem != _nodeHash.end()) { - SharedNodePointer node = nodeItem.value(); - - node->getMutex().lock(); - - if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) { - // call our private method to kill this node (removes it and emits the right signal) - nodeItem = killNodeAtHashIterator(nodeItem); - } else { - // we didn't kill this node, push the iterator forwards - ++nodeItem; - } - - node->getMutex().unlock(); - } - - _nodeHashMutex.unlock(); -} - -const QString QSETTINGS_GROUP_NAME = "NodeList"; -const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; - -void NodeList::loadData(QSettings *settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); - - if (domainServerHostname.size() > 0) { - _domainInfo.setHostname(domainServerHostname); - } else { - _domainInfo.setHostname(DEFAULT_DOMAIN_HOSTNAME); - } - - settings->endGroup(); -} - -void NodeList::saveData(QSettings* settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - if (_domainInfo.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { - // the user is using a different hostname, store it - settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainInfo.getHostname())); - } else { - // the user has switched back to default, remove the current setting - settings->remove(DOMAIN_SERVER_SETTING_KEY); - } - - settings->endGroup(); -} diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index db62f33f06..956f78204f 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -15,10 +15,6 @@ #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#endif - #ifdef __APPLE__ #include #endif @@ -26,7 +22,6 @@ #include #include "OctalCode.h" -#include "PacketHeaders.h" #include "SharedUtil.h" quint64 usecTimestamp(const timeval *time) { @@ -44,7 +39,7 @@ quint64 usecTimestampNow() { return (now.tv_sec * 1000000 + now.tv_usec) + ::usecTimestampNowAdjust; } -float randFloat () { +float randFloat() { return (rand() % 10000)/10000.f; } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index c909b80b13..a2f98549ad 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -30,8 +30,6 @@ #include #endif -#include "PacketHeaders.h" - const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; typedef unsigned char rgbColor[BYTES_PER_COLOR]; @@ -45,7 +43,6 @@ struct xColor { unsigned char blue; }; - static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f; @@ -69,8 +66,6 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; const int BITS_IN_BYTE = 8; -const int MAX_PACKET_SIZE = 1500; - quint64 usecTimestamp(const timeval *time); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); diff --git a/libraries/shared/src/Syssocket.h b/libraries/shared/src/Syssocket.h deleted file mode 100644 index 36713a2b7b..0000000000 --- a/libraries/shared/src/Syssocket.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __Syssocket__ -#define __Syssocket__ - -#ifdef _WIN32 -#define WINSOCK_API_LINKAGE -#include -#ifndef _timeval_ -#define _timeval_ -#endif -typedef SSIZE_T ssize_t; -typedef ULONG32 in_addr_t; -typedef USHORT in_port_t; -typedef USHORT uint16_t; -typedef ULONG32 socklen_t; - -#endif _Win32 - -#endif __Syssocket__ \ No newline at end of file diff --git a/libraries/shared/src/Systime.cpp b/libraries/shared/src/Systime.cpp index 7d8764e3f9..ab32821a0f 100644 --- a/libraries/shared/src/Systime.cpp +++ b/libraries/shared/src/Systime.cpp @@ -1,14 +1,61 @@ -#ifdef _WIN32 -#include -#define _timeval_ +// +// Systime.cpp +// libraries/shared/src +// +// Copyright 2013 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 +// + +#ifdef WIN32 + #include "Systime.h" - int gettimeofday( timeval* p_tv, timezone* p_tz ) - { - int tt = timeGetTime(); +/** + * gettimeofday + * Implementation according to: + * The Open Group Base Specifications Issue 6 + * IEEE Std 1003.1, 2004 Edition + */ + +/** + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Contributed by: + * Danny Smith + */ - p_tv->tv_sec = tt / 1000; - p_tv->tv_usec = tt % 1000 * 1000; - return 0; - } -#endif +#define WIN32_LEAN_AND_MEAN +#include + +/** Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */ +#define _W32_FT_OFFSET (116444736000000000ULL) + +int gettimeofday(timeval* p_tv, timezone* p_tz) { + + union { + unsigned long long ns100; /**time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if (p_tv) { + GetSystemTimeAsFileTime (&_now.ft); + p_tv->tv_usec=(long)((_now.ns100 / 10ULL) % 1000000ULL ); + p_tv->tv_sec= (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000ULL); + } + + /** Always return 0 as per Open Group Base Specifications Issue 6. + Do not set errno on error. */ + return 0; +} + +#endif \ No newline at end of file diff --git a/libraries/shared/src/Systime.h b/libraries/shared/src/Systime.h index 1d25de0b80..3098f09ecd 100644 --- a/libraries/shared/src/Systime.h +++ b/libraries/shared/src/Systime.h @@ -1,39 +1,27 @@ -#ifndef __Systime__ -#define __Systime__ +// +// Systime.h +// libraries/shared/src +// +// Copyright 2013 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_Systime_h +#define hifi_Systime_h -#ifdef _WIN32 +#ifdef WIN32 -#ifdef _WINSOCK2API_ -#define _timeval_ -#endif - -#ifndef _timeval_ -#define _timeval_ -/* - * Structure returned by gettimeofday(2) system call, - * and used in other calls. - */ - -// this is a bit of a hack for now, but sometimes on windows -// we need timeval defined here, sometimes we get it -// from winsock.h -#ifdef WANT_TIMEVAL -struct timeval { - long tv_sec; /* seconds */ - long tv_usec; /* and microseconds */ -}; -#endif - -#endif _timeval_ +#include struct timezone { int tz_minuteswest; /* minutes west of Greenwich */ int tz_dsttime; /* type of dst correction */ }; -int gettimeofday( struct timeval* p_tv, struct timezone* p_tz ); +int gettimeofday(struct timeval* p_tv, struct timezone* p_tz); -#endif _Win32 +#endif -#endif __Systime__ +#endif // hifi_Systime_h \ No newline at end of file diff --git a/libraries/voxels/CMakeLists.txt b/libraries/voxels/CMakeLists.txt index afc0b34b64..bdba388594 100644 --- a/libraries/voxels/CMakeLists.txt +++ b/libraries/voxels/CMakeLists.txt @@ -24,9 +24,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets Qt5::Script) +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets Qt5::Script) \ No newline at end of file diff --git a/voxel-edit/CMakeLists.txt b/voxel-edit/CMakeLists.txt index 0de8fd059e..cc0a122bf0 100644 --- a/voxel-edit/CMakeLists.txt +++ b/voxel-edit/CMakeLists.txt @@ -31,8 +31,19 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +# link in the hifi networking library +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link GnuTLS +find_package(GnuTLS REQUIRED) + IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) + + # add a definition for ssize_t so that windows doesn't bail on gnutls.h + add_definitions(-Dssize_t=long) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file