Merge branch 'master' of https://github.com/highfidelity/hifi into 19561

This commit is contained in:
Thijs Wenker 2014-04-15 00:40:48 +02:00
commit 1634e574d7
97 changed files with 3094 additions and 1839 deletions

View file

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

View file

@ -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}")

View file

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

View file

@ -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}")

View file

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

View file

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

View file

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

View file

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

View file

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

View 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 ()

View file

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

View file

@ -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}")

View 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)
{
}

View 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

View file

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

View file

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

View file

@ -20,7 +20,8 @@
DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(),
_staticAssignmentUUID(),
_statsJSONObject()
_statsJSONObject(),
_sendingSockAddr()
{
}

View file

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

View file

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

View file

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

View 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

View file

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

View file

@ -13,7 +13,6 @@
#define hifi_Audio_h
#ifdef _WIN32
#define WANT_TIMEVAL
#include <Systime.h>
#endif

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,6 @@
//
#ifdef _WIN32
#define WANT_TIMEVAL
#include <Systime.h>
#endif

View file

@ -10,7 +10,6 @@
//
#ifdef _WIN32
#define WANT_TIMEVAL
#include <Systime.h>
#endif

View file

@ -13,7 +13,6 @@
#define hifi_BandwidthMeter_h
#ifdef _WIN32
#define WANT_TIMEVAL
#include <Systime.h>
#endif

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}")

View file

@ -20,6 +20,7 @@
#include <QtNetwork/QNetworkReply>
#include <qendian.h>
#include <LimitedNodeList.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"

View file

@ -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}")

View file

@ -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}")

View file

@ -13,7 +13,7 @@
#include <QtDebug>
#include <SharedUtil.h>
#include <LimitedNodeList.h>
#include "DatagramSequencer.h"
#include "MetavoxelMessages.h"

View 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}")

View file

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

View file

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

View file

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

View file

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

View 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);
}

View 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

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

View 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

View file

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

View file

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

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

View file

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

View 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)
{
}

View 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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View file

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

View file

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

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}")

View file

@ -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}")

View file

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

View file

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

View file

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

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}")