mirror of
https://github.com/overte-org/overte.git
synced 2025-04-11 04:12:09 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into 19561
This commit is contained in:
commit
1634e574d7
97 changed files with 3094 additions and 1839 deletions
44
BUILD.md
44
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.
|
||||
|
|
|
@ -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}")
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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}")
|
|
@ -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);
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <Logging.h>
|
||||
|
@ -32,64 +34,58 @@ int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#include "Systime.h"
|
||||
#include <math.h>
|
||||
#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;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
AvatarMixerClientData::AvatarMixerClientData() :
|
||||
|
|
41
cmake/modules/FindGnuTLS.cmake
Normal file
41
cmake/modules/FindGnuTLS.cmake
Normal file
|
@ -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 ()
|
|
@ -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 ()
|
|
@ -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)
|
||||
# link QtNetwork and GnuTLS
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}")
|
18
domain-server/src/DTLSServerSession.cpp
Normal file
18
domain-server/src/DTLSServerSession.cpp
Normal file
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
24
domain-server/src/DTLSServerSession.h
Normal file
24
domain-server/src/DTLSServerSession.h
Normal file
|
@ -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 <gnutls/dtls.h>
|
||||
|
||||
#include <DTLSSession.h>
|
||||
|
||||
class DTLSServerSession : public DTLSSession {
|
||||
public:
|
||||
DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket);
|
||||
};
|
||||
|
||||
#endif // hifi_DTLSServerSession_h
|
|
@ -9,8 +9,6 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -19,13 +17,17 @@
|
|||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#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<Assignment::Type> parsedTypes(QSet<Assignment::Type>() << 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<Assignment::Type>& excludedTypes) {
|
||||
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& 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<Assignment::Type>& 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<Assignment::Type> 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 QSet<Ass
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr) {
|
||||
// this is a node we do not recognize and we need authentication - ask them to do so
|
||||
// by providing them the hostname they should authenticate with
|
||||
QByteArray authenticationRequestPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerAuthRequest);
|
||||
|
||||
QDataStream authPacketStream(&authenticationRequestPacket, QIODevice::Append);
|
||||
authPacketStream << _nodeAuthenticationURL;
|
||||
|
||||
qDebug() << "Asking node at" << senderSockAddr << "to authenticate.";
|
||||
|
||||
// send the authentication request back to the node
|
||||
NodeList::getInstance()->getNodeSocket().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<DomainServerNodeData*>(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<DomainServerNodeData*>(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<DomainServerNodeData*>(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<const char*>(&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<DomainServerNodeData*>(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<DomainServerNodeData*>(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<DomainServerNodeData*>(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<DomainServerNodeData*>(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;
|
||||
}
|
||||
|
|
|
@ -20,18 +20,21 @@
|
|||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
|
||||
typedef QSharedPointer<Assignment> 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<Assignment::Type>& excludedTypes);
|
||||
void readConfigFile(const QString& path, QSet<Assignment::Type>& excludedTypes);
|
||||
QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName);
|
||||
void parseAssignmentConfigs(QSet<Assignment::Type>& 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<Assignment::Type>& excludedTypes);
|
||||
|
||||
SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
|
||||
|
@ -76,17 +84,15 @@ private:
|
|||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _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<QString, QJsonObject> _redeemedTokenResponses;
|
||||
private slots:
|
||||
void requestCreationFromDataServer();
|
||||
void processCreateResponseFromDataServer(const QJsonObject& jsonObject);
|
||||
void processTokenRedeemResponse(const QJsonObject& jsonObject);
|
||||
|
||||
void readAvailableDatagrams();
|
||||
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServer_h
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
DomainServerNodeData::DomainServerNodeData() :
|
||||
_sessionSecretHash(),
|
||||
_staticAssignmentUUID(),
|
||||
_statsJSONObject()
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr()
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QtCore/QHash>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <NodeData.h>
|
||||
|
||||
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<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
|
||||
private:
|
||||
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
|
||||
|
@ -36,6 +40,7 @@ private:
|
|||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _staticAssignmentUUID;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ elseif (UNIX)
|
|||
# include the right GL headers for UNIX
|
||||
set(GL_HEADERS "#include <GL/gl.h>\n#include <GL/glut.h>\n#include <GL/glext.h>")
|
||||
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 <windowshacks.h>\n#include <GL/glew.h>\n#include <GL/glut.h>")
|
||||
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)
|
||||
|
|
79
interface/resources/images/close_down.svg
Normal file
79
interface/resources/images/close_down.svg
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="44px"
|
||||
height="44px"
|
||||
viewBox="0 0 44 44"
|
||||
version="1.1"
|
||||
id="svg3085"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="close_hover.svg">
|
||||
<metadata
|
||||
id="metadata3099">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Slice 1</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="814"
|
||||
inkscape:window-height="783"
|
||||
id="namedview3097"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.727273"
|
||||
inkscape:cx="14.784087"
|
||||
inkscape:cy="19.379049"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Page-1" />
|
||||
<title
|
||||
id="title3087">Slice 1</title>
|
||||
<description
|
||||
id="description3089">Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
|
||||
<defs
|
||||
id="defs3091" />
|
||||
<g
|
||||
id="Page-1"
|
||||
sketch:type="MSPage"
|
||||
stroke-width="1"
|
||||
stroke="none"
|
||||
fill-rule="evenodd"
|
||||
fill="none">
|
||||
<g
|
||||
id="close"
|
||||
sketch:type="MSLayerGroup"
|
||||
fill="#CCCCCC">
|
||||
<path
|
||||
d="M0.286382588,32.6200761 L32.6200759,0.286382745 C33.0019275,-0.0954590326 33.6210173,-0.0954590326 34.0028688,0.286382745 L43.380667,9.66418097 C43.7625088,10.0460227 43.7625088,10.6651125 43.380667,11.0469543 L43.380667,11.0469543 L11.0469639,43.3806574 C10.6651221,43.7624992 10.0460226,43.7624992 9.66418081,43.3806574 L0.286382588,34.0028592 C-0.0954591894,33.6210076 -0.0954591894,33.0019179 0.286382588,32.6200761 L0.286382588,32.6200761 L0.286382588,32.6200761 Z"
|
||||
id="path16"
|
||||
sketch:type="MSShapeGroup"
|
||||
style="fill:#e6e6e6" />
|
||||
<path
|
||||
d="M32.6200759,43.3806574 L0.286382588,11.0469543 C-0.0954591894,10.6651125 -0.0954591894,10.0460227 0.286382588,9.66418097 L9.66418081,0.286382745 C10.0460226,-0.0954590326 10.6651221,-0.0954590326 11.0469639,0.286382745 L43.380667,32.6200761 C43.7625088,33.0019179 43.7625088,33.6210076 43.380667,34.0028592 L34.0028688,43.3806574 C33.6210173,43.7624992 33.0019275,43.7624992 32.6200759,43.3806574 L32.6200759,43.3806574 Z"
|
||||
id="path18"
|
||||
sketch:type="MSShapeGroup"
|
||||
style="fill:#e6e6e6" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -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() {
|
||||
|
@ -3046,8 +3051,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);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_Audio_h
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WANT_TIMEVAL
|
||||
#include <Systime.h>
|
||||
#endif
|
||||
|
||||
|
|
|
@ -783,23 +783,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());
|
||||
|
@ -992,7 +991,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);
|
||||
}
|
||||
}
|
||||
|
@ -1072,18 +1071,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -78,7 +78,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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
//
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WANT_TIMEVAL
|
||||
#include <Systime.h>
|
||||
#endif
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
//
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WANT_TIMEVAL
|
||||
#include <Systime.h>
|
||||
#endif
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_BandwidthMeter_h
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WANT_TIMEVAL
|
||||
#include <Systime.h>
|
||||
#endif
|
||||
|
||||
|
|
44
interface/src/ui/ChatMessageArea.cpp
Normal file
44
interface/src/ui/ChatMessageArea.cpp
Normal file
|
@ -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 <QAbstractTextDocumentLayout>
|
||||
#include <QWheelEvent>
|
||||
|
||||
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();
|
||||
}
|
33
interface/src/ui/ChatMessageArea.h
Normal file
33
interface/src/ui/ChatMessageArea.h
Normal file
|
@ -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 <QTextBrowser>
|
||||
|
||||
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
|
|
@ -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, "<a href=\"\\1\">\\1</a>"));
|
||||
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, "<a href=\"\\1\">\\1</a>"));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 '-'
|
||||
|
|
|
@ -135,13 +135,23 @@
|
|||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">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)
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/close.svg</normaloff>../resources/images/close.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
|
|
@ -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}")
|
||||
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}")
|
|
@ -20,6 +20,7 @@
|
|||
#include <QtNetwork/QNetworkReply>
|
||||
#include <qendian.h>
|
||||
|
||||
#include <LimitedNodeList.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "AudioRingBuffer.h"
|
||||
|
|
|
@ -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)
|
||||
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}")
|
|
@ -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}")
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DatagramSequencer.h"
|
||||
#include "MetavoxelMessages.h"
|
||||
|
|
29
libraries/networking/CMakeLists.txt
Normal file
29
libraries/networking/CMakeLists.txt
Normal file
|
@ -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}")
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
83
libraries/networking/src/DTLSClientSession.cpp
Normal file
83
libraries/networking/src/DTLSClientSession.cpp
Normal file
|
@ -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<DomainHandler*>(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<char *>(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);
|
||||
}
|
30
libraries/networking/src/DTLSClientSession.h
Normal file
30
libraries/networking/src/DTLSClientSession.h
Normal file
|
@ -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
|
145
libraries/networking/src/DTLSSession.cpp
Normal file
145
libraries/networking/src/DTLSSession.cpp
Normal file
|
@ -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 <gnutls/dtls.h>
|
||||
|
||||
#include "NodeList.h"
|
||||
#include "DTLSSession.h"
|
||||
|
||||
int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
DTLSSession* session = static_cast<DTLSSession*>(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<DTLSSession*>(ptr);
|
||||
QUdpSocket& dtlsSocket = session->_dtlsSocket;
|
||||
|
||||
HifiSockAddr pulledSockAddr;
|
||||
qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast<char*>(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());
|
||||
}
|
44
libraries/networking/src/DTLSSession.h
Normal file
44
libraries/networking/src/DTLSSession.h
Normal file
|
@ -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 <QtNetwork/QUdpSocket>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#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
|
|
@ -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.
|
|
@ -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.
|
183
libraries/networking/src/DomainHandler.cpp
Normal file
183
libraries/networking/src/DomainHandler.cpp
Normal file
|
@ -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 <gnutls/dtls.h>
|
||||
|
||||
#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();
|
||||
}
|
|
@ -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 <QtCore/QObject>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
#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
|
31
libraries/networking/src/DummyDTLSSession.cpp
Normal file
31
libraries/networking/src/DummyDTLSSession.cpp
Normal file
|
@ -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<DummyDTLSSession*>(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<const char*>(buffer), size,
|
||||
session->_destinationSocket.getAddress(), session->_destinationSocket.getPort());
|
||||
}
|
||||
|
||||
DummyDTLSSession::DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket) :
|
||||
_dtlsSocket(dtlsSocket),
|
||||
_destinationSocket(destinationSocket)
|
||||
{
|
||||
|
||||
}
|
34
libraries/networking/src/DummyDTLSSession.h
Normal file
34
libraries/networking/src/DummyDTLSSession.h
Normal file
|
@ -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 <QtNetwork/QUdpSocket>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#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
|
|
@ -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 <QtCore/QDataStream>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
#include <QtNetwork/QNetworkInterface>
|
||||
|
||||
#include "HifiSockAddr.h"
|
||||
|
||||
static int hifiSockAddrMetaTypeId = qMetaTypeId<HifiSockAddr>();
|
||||
|
||||
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<const sockaddr_in*>(sockaddr)->sin_port);
|
||||
} else {
|
||||
_port = ntohs(reinterpret_cast<const sockaddr_in6*>(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);
|
||||
}
|
|
@ -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 <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include <QtNetwork/QHostAddress>
|
||||
|
||||
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)
|
444
libraries/networking/src/LimitedNodeList.cpp
Normal file
444
libraries/networking/src/LimitedNodeList.cpp
Normal file
|
@ -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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
#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<SharedNodePointer>();
|
||||
} 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<char*>(&oldBufferSize), &sizeOfInt);
|
||||
|
||||
setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&numSendBytes),
|
||||
sizeof(numSendBytes));
|
||||
|
||||
int newBufferSize = 0;
|
||||
getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&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<QUuid, PacketType> 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();
|
||||
}
|
|
@ -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 <netinet/in.h>
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include <iterator>
|
||||
|
||||
|
@ -32,12 +27,14 @@
|
|||
#include <QtNetwork/QHostAddress>
|
||||
#include <QtNetwork/QUdpSocket>
|
||||
|
||||
#include "DomainInfo.h"
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#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<NodeType_t> NodeSet;
|
||||
|
@ -56,57 +50,31 @@ typedef QSharedPointer<Node> SharedNodePointer;
|
|||
typedef QHash<QUuid, SharedNodePointer> 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
|
|
@ -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.
|
|
@ -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 <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
const int LOGSTASH_UDP_PORT = 9500;
|
|
@ -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.
|
|
@ -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.
|
|
@ -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 <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#else
|
||||
#include <arpa/inet.h> // not available on windows, apparently not needed on mac
|
||||
#endif
|
||||
|
||||
#include "Node.h"
|
||||
#include "SharedUtil.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 <ostream>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QUuid>
|
|
@ -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.
|
|
@ -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.
|
590
libraries/networking/src/NodeList.cpp
Normal file
590
libraries/networking/src/NodeList.cpp
Normal file
|
@ -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 <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
#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<SharedNodePointer>();
|
||||
} 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();
|
||||
}
|
114
libraries/networking/src/NodeList.h
Normal file
114
libraries/networking/src/NodeList.h
Normal file
|
@ -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 <stdint.h>
|
||||
#include <iterator>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h> // not on windows, not needed for mac or windows
|
||||
#endif
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QtNetwork/QUdpSocket>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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) {
|
|
@ -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 <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#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<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< 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);
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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}")
|
|
@ -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}")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 <QtCore/QJsonObject>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
98
libraries/shared/src/HifiConfigVariantMap.cpp
Normal file
98
libraries/shared/src/HifiConfigVariantMap.cpp
Normal file
|
@ -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 <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#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;
|
||||
}
|
22
libraries/shared/src/HifiConfigVariantMap.h
Normal file
22
libraries/shared/src/HifiConfigVariantMap.h
Normal file
|
@ -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 <QtCore/QStringList>
|
||||
|
||||
class HifiConfigVariantMap {
|
||||
public:
|
||||
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
||||
};
|
||||
|
||||
#endif // hifi_HifiConfigVariantMap_h
|
|
@ -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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
#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<SharedNodePointer>();
|
||||
} 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<char*>(&oldBufferSize), &sizeOfInt);
|
||||
|
||||
setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&numSendBytes),
|
||||
sizeof(numSendBytes));
|
||||
|
||||
int newBufferSize = 0;
|
||||
getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&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<QUuid, PacketType> 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<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< 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();
|
||||
}
|
|
@ -15,10 +15,6 @@
|
|||
#include <cctype>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
@ -26,7 +22,6 @@
|
|||
#include <QtCore/QDebug>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
#include <sys/time.h>
|
||||
#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);
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
#ifndef __Syssocket__
|
||||
#define __Syssocket__
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WINSOCK_API_LINKAGE
|
||||
#include <winsock2.h>
|
||||
#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__
|
|
@ -1,14 +1,61 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#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 <dannysmith@users.sourceforge.net>
|
||||
*/
|
||||
|
||||
p_tv->tv_sec = tt / 1000;
|
||||
p_tv->tv_usec = tt % 1000 * 1000;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
/** 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
|
|
@ -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 <winsock2.h>
|
||||
|
||||
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
|
|
@ -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)
|
|
@ -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)
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}")
|
Loading…
Reference in a new issue