mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 06:44:06 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into uniform-story-handling
This commit is contained in:
commit
4357bb391d
25 changed files with 848 additions and 182 deletions
|
@ -380,6 +380,14 @@
|
|||
"default": "0",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "maximum_user_capacity_redirect_location",
|
||||
"label": "Redirect to Location on Maximum Capacity",
|
||||
"help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
|
|
|
@ -317,6 +317,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
}
|
||||
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location";
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
|
@ -363,7 +364,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
|
@ -372,8 +373,16 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
|
||||
// we can't allow this user to connect because we are at max capacity
|
||||
QString redirectOnMaxCapacity;
|
||||
const QVariant* redirectOnMaxCapacityVariant =
|
||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION);
|
||||
if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert<QString>()) {
|
||||
redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString();
|
||||
qDebug() << "Redirection domain:" << redirectOnMaxCapacity;
|
||||
}
|
||||
|
||||
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers, redirectOnMaxCapacity);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to max capacity:" << username;
|
||||
#endif
|
||||
|
@ -623,22 +632,30 @@ void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr&
|
|||
}
|
||||
|
||||
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason reasonCode) {
|
||||
DomainHandler::ConnectionRefusedReason reasonCode,
|
||||
QString extraInfo) {
|
||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||
QByteArray utfString = reason.toUtf8();
|
||||
quint16 payloadSize = utfString.size();
|
||||
QByteArray utfReasonString = reason.toUtf8();
|
||||
quint16 reasonSize = utfReasonString.size();
|
||||
|
||||
QByteArray utfExtraInfo = extraInfo.toUtf8();
|
||||
quint16 extraInfoSize = utfExtraInfo.size();
|
||||
|
||||
// setup the DomainConnectionDenied packet
|
||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
||||
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
||||
sizeof(uint8_t) + // reasonCode
|
||||
reasonSize + sizeof(reasonSize) +
|
||||
extraInfoSize + sizeof(extraInfoSize));
|
||||
|
||||
// pack in the reason the connection was denied (the client displays this)
|
||||
if (payloadSize > 0) {
|
||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||
connectionDeniedPacket->writePrimitive(reasonCodeWire);
|
||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||
connectionDeniedPacket->write(utfString);
|
||||
}
|
||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||
connectionDeniedPacket->writePrimitive(reasonCodeWire);
|
||||
connectionDeniedPacket->writePrimitive(reasonSize);
|
||||
connectionDeniedPacket->write(utfReasonString);
|
||||
|
||||
// write the extra info as well
|
||||
connectionDeniedPacket->writePrimitive(extraInfoSize);
|
||||
connectionDeniedPacket->write(utfExtraInfo);
|
||||
|
||||
// send the packet off
|
||||
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||
|
|
|
@ -88,7 +88,8 @@ private:
|
|||
|
||||
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
|
||||
static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown);
|
||||
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown,
|
||||
QString extraInfo = QString());
|
||||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <BuildInfo.h>
|
||||
|
@ -66,8 +67,11 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_webAuthenticationStateSet(),
|
||||
_cookieSessionHash(),
|
||||
_automaticNetworkingSetting(),
|
||||
_settingsManager()
|
||||
_settingsManager(),
|
||||
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
|
||||
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
|
||||
{
|
||||
parseCommandLine();
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
|
||||
LogUtils::init();
|
||||
|
@ -159,6 +163,46 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qDebug() << "domain-server is running";
|
||||
}
|
||||
|
||||
void DomainServer::parseCommandLine() {
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("High Fidelity Domain Server");
|
||||
parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
|
||||
parser.addOption(iceServerAddressOption);
|
||||
|
||||
const QCommandLineOption domainIDOption("d", "domain-server uuid");
|
||||
parser.addOption(domainIDOption);
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qWarning() << parser.errorText() << endl;
|
||||
parser.showHelp();
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
if (parser.isSet(iceServerAddressOption)) {
|
||||
// parse the IP and port combination for this target
|
||||
QString hostnamePortString = parser.value(iceServerAddressOption);
|
||||
|
||||
_iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':'));
|
||||
_iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt();
|
||||
if (_iceServerPort == 0) {
|
||||
_iceServerPort = ICE_SERVER_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (_iceServerAddr.isEmpty()) {
|
||||
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.isSet(domainIDOption)) {
|
||||
_overridingDomainID = QUuid(parser.value(domainIDOption));
|
||||
_overrideDomainID = true;
|
||||
qDebug() << "domain-server ID is" << _overridingDomainID;
|
||||
}
|
||||
}
|
||||
|
||||
DomainServer::~DomainServer() {
|
||||
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down
|
||||
DependencyManager::destroy<LimitedNodeList>();
|
||||
|
@ -166,7 +210,7 @@ DomainServer::~DomainServer() {
|
|||
|
||||
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
|
||||
if (!quitMessage.isEmpty()) {
|
||||
qCritical() << qPrintable(quitMessage);
|
||||
qWarning() << qPrintable(quitMessage);
|
||||
}
|
||||
|
||||
QCoreApplication::exit(exitCode);
|
||||
|
@ -307,7 +351,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
|
||||
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
||||
if (!domainObject.isEmpty()) {
|
||||
auto id = domainObject[ID_KEY].toString();
|
||||
auto id = _overrideDomainID ? _overridingDomainID.toString() : domainObject[ID_KEY].toString();
|
||||
auto name = domainObject[NAME_KEY].toString();
|
||||
auto key = domainObject[KEY_KEY].toString();
|
||||
|
||||
|
@ -415,24 +459,30 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT;
|
||||
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort);
|
||||
|
||||
|
||||
// set our LimitedNodeList UUID to match the UUID from our config
|
||||
// nodes will currently use this to add resources to data-web that relate to our domain
|
||||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
bool isMetaverseDomain = false;
|
||||
if (_overrideDomainID) {
|
||||
nodeList->setSessionUUID(_overridingDomainID);
|
||||
isMetaverseDomain = true; // assume metaverse domain
|
||||
} else {
|
||||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
}
|
||||
}
|
||||
|
||||
// if we have an ID, we'll assume we're a metaverse domain
|
||||
// now see if we think we're a temp domain (we have an API key) or a full domain
|
||||
if (isMetaverseDomain) {
|
||||
// see if we think we're a temp domain (we have an API key) or a full domain
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (temporaryDomainKey.isEmpty()) {
|
||||
_type = MetaverseDomain;
|
||||
} else {
|
||||
_type = MetaverseTemporaryDomain;
|
||||
}
|
||||
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
}
|
||||
|
||||
connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
|
||||
|
@ -548,7 +598,6 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -606,12 +655,11 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
|
||||
void DomainServer::updateICEServerAddresses() {
|
||||
if (_iceAddressLookupID == -1) {
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(ICE_SERVER_DEFAULT_HOSTNAME, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
|
||||
// check for configs from the command line, these take precedence
|
||||
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
||||
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ signals:
|
|||
|
||||
private:
|
||||
const QUuid& getID();
|
||||
void parseCommandLine();
|
||||
|
||||
void setupNodeListAndAssignments();
|
||||
bool optionallySetupOAuth();
|
||||
|
@ -205,6 +206,11 @@ private:
|
|||
|
||||
friend class DomainGatekeeper;
|
||||
friend class DomainMetadata;
|
||||
|
||||
QString _iceServerAddr;
|
||||
int _iceServerPort;
|
||||
bool _overrideDomainID { false }; // should we override the domain-id from settings?
|
||||
QUuid _overridingDomainID { QUuid() }; // what should we override it with?
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1239,8 +1239,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
firstRun.set(false);
|
||||
}
|
||||
|
||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) {
|
||||
switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) {
|
||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||
DomainHandler::ConnectionRefusedReason reasonCode = static_cast<DomainHandler::ConnectionRefusedReason>(reasonCodeInt);
|
||||
|
||||
if (reasonCode == DomainHandler::ConnectionRefusedReason::TooManyUsers && !extraInfo.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(extraInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (reasonCode) {
|
||||
case DomainHandler::ConnectionRefusedReason::ProtocolMismatch:
|
||||
case DomainHandler::ConnectionRefusedReason::TooManyUsers:
|
||||
case DomainHandler::ConnectionRefusedReason::Unknown: {
|
||||
|
|
|
@ -375,7 +375,7 @@ private slots:
|
|||
void nodeKilled(SharedNodePointer node);
|
||||
static void packetSent(quint64 length);
|
||||
void updateDisplayMode();
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reason);
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
|
||||
|
||||
private:
|
||||
static void initDisplay();
|
||||
|
|
|
@ -58,7 +58,7 @@ public slots:
|
|||
signals:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void svoImportRequested(const QString& url);
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reasonCode);
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo);
|
||||
void snapshotTaken(const QString& path, bool notify);
|
||||
void snapshotShared(const QString& error);
|
||||
|
||||
|
|
|
@ -278,12 +278,18 @@ void setupPreferences() {
|
|||
preferences->addPreference(preference);
|
||||
}
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
{
|
||||
auto getter = []()->bool { return DependencyManager::get<AudioClient>()->isSimulatingJitter(); };
|
||||
auto setter = [](bool value) { return DependencyManager::get<AudioClient>()->setIsSimulatingJitter(value); };
|
||||
auto preference = new CheckPreference(AUDIO, "Packet jitter simulator", getter, setter);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->float { return DependencyManager::get<AudioClient>()->getGateThreshold(); };
|
||||
auto setter = [](float value) { return DependencyManager::get<AudioClient>()->setGateThreshold(value); };
|
||||
auto preference = new SpinnerPreference(AUDIO, "Debug gate threshold", getter, setter);
|
||||
auto preference = new SpinnerPreference(AUDIO, "Packet throttle threshold", getter, setter);
|
||||
preference->setMin(1);
|
||||
preference->setMax((float)100);
|
||||
preference->setMax(200);
|
||||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
|
|
@ -56,8 +56,6 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
|
|||
static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; };
|
||||
static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; };
|
||||
|
||||
static const int DEFAULT_AUDIO_OUTPUT_GATE_THRESHOLD = 1;
|
||||
|
||||
Setting::Handle<bool> dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS);
|
||||
Setting::Handle<int> maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED);
|
||||
Setting::Handle<int> staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames",
|
||||
|
@ -102,8 +100,7 @@ private:
|
|||
|
||||
AudioClient::AudioClient() :
|
||||
AbstractAudioInterface(),
|
||||
_gateThreshold("audioOutputGateThreshold", DEFAULT_AUDIO_OUTPUT_GATE_THRESHOLD),
|
||||
_gate(this, _gateThreshold.get()),
|
||||
_gate(this),
|
||||
_audioInput(NULL),
|
||||
_desiredInputFormat(),
|
||||
_inputFormat(),
|
||||
|
@ -551,31 +548,53 @@ void AudioClient::handleAudioDataPacket(QSharedPointer<ReceivedMessage> message)
|
|||
}
|
||||
}
|
||||
|
||||
AudioClient::Gate::Gate(AudioClient* audioClient, int threshold) :
|
||||
_audioClient(audioClient),
|
||||
_threshold(threshold) {}
|
||||
AudioClient::Gate::Gate(AudioClient* audioClient) :
|
||||
_audioClient(audioClient) {}
|
||||
|
||||
void AudioClient::Gate::setIsSimulatingJitter(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
flush();
|
||||
_isSimulatingJitter = enable;
|
||||
}
|
||||
|
||||
void AudioClient::Gate::setThreshold(int threshold) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
flush();
|
||||
_threshold = std::max(threshold, 1);
|
||||
}
|
||||
|
||||
void AudioClient::Gate::insert(QSharedPointer<ReceivedMessage> message) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
// Short-circuit for normal behavior
|
||||
if (_threshold == 1) {
|
||||
if (_threshold == 1 && !_isSimulatingJitter) {
|
||||
_audioClient->_receivedAudioStream.parseData(*message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle the current packet until the next flush
|
||||
_queue.push(message);
|
||||
_index++;
|
||||
|
||||
if (_index % _threshold == 0) {
|
||||
// When appropriate, flush all held packets to the received audio stream
|
||||
if (_isSimulatingJitter) {
|
||||
// The JITTER_FLUSH_CHANCE defines the discrete probability density function of jitter (ms),
|
||||
// where f(t) = pow(1 - JITTER_FLUSH_CHANCE, (t / 10) * JITTER_FLUSH_CHANCE
|
||||
// for t (ms) = 10, 20, ... (because typical packet timegap is 10ms),
|
||||
// because there is a JITTER_FLUSH_CHANCE of any packet instigating a flush of all held packets.
|
||||
static const float JITTER_FLUSH_CHANCE = 0.6f;
|
||||
// It is set at 0.6 to give a low chance of spikes (>30ms, 2.56%) so that they are obvious,
|
||||
// but settled within the measured 5s window in audio network stats.
|
||||
if (randFloat() < JITTER_FLUSH_CHANCE) {
|
||||
flush();
|
||||
}
|
||||
} else if (!(_index % _threshold)) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::Gate::flush() {
|
||||
// Send all held packets to the received audio stream to be (eventually) played
|
||||
while (!_queue.empty()) {
|
||||
_audioClient->_receivedAudioStream.parseData(*_queue.front());
|
||||
_queue.pop();
|
||||
|
|
|
@ -132,6 +132,9 @@ public:
|
|||
int getOutputStarveDetectionThreshold() { return _outputStarveDetectionThreshold.get(); }
|
||||
void setOutputStarveDetectionThreshold(int threshold) { _outputStarveDetectionThreshold.set(threshold); }
|
||||
|
||||
bool isSimulatingJitter() { return _gate.isSimulatingJitter(); }
|
||||
void setIsSimulatingJitter(bool enable) { _gate.setIsSimulatingJitter(enable); }
|
||||
|
||||
int getGateThreshold() { return _gate.getThreshold(); }
|
||||
void setGateThreshold(int threshold) { _gate.setThreshold(threshold); }
|
||||
|
||||
|
@ -230,7 +233,10 @@ private:
|
|||
|
||||
class Gate {
|
||||
public:
|
||||
Gate(AudioClient* audioClient, int threshold);
|
||||
Gate(AudioClient* audioClient);
|
||||
|
||||
bool isSimulatingJitter() { return _isSimulatingJitter; }
|
||||
void setIsSimulatingJitter(bool enable);
|
||||
|
||||
int getThreshold() { return _threshold; }
|
||||
void setThreshold(int threshold);
|
||||
|
@ -242,11 +248,13 @@ private:
|
|||
|
||||
AudioClient* _audioClient;
|
||||
std::queue<QSharedPointer<ReceivedMessage>> _queue;
|
||||
std::mutex _mutex;
|
||||
|
||||
int _index{ 0 };
|
||||
int _threshold;
|
||||
int _threshold{ 1 };
|
||||
bool _isSimulatingJitter{ false };
|
||||
};
|
||||
|
||||
Setting::Handle<int> _gateThreshold;
|
||||
Gate _gate;
|
||||
|
||||
Mutex _injectorsMutex;
|
||||
|
|
|
@ -198,15 +198,10 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
#endif
|
||||
|
||||
if (!_webSurface) {
|
||||
#if defined(Q_OS_LINUX)
|
||||
// these don't seem to work on Linux
|
||||
return;
|
||||
#else
|
||||
if (!buildWebSurface(static_cast<EntityTreeRenderer*>(args->_renderer))) {
|
||||
return;
|
||||
}
|
||||
_fadeStartTime = usecTimestampNow();
|
||||
#endif
|
||||
}
|
||||
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
|
|
|
@ -402,13 +402,18 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
auto reasonText = message->readWithoutCopy(reasonSize);
|
||||
QString reasonMessage = QString::fromUtf8(reasonText);
|
||||
|
||||
quint16 extraInfoSize;
|
||||
message->readPrimitive(&extraInfoSize);
|
||||
auto extraInfoUtf8= message->readWithoutCopy(extraInfoSize);
|
||||
QString extraInfo = QString::fromUtf8(extraInfoUtf8);
|
||||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage;
|
||||
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage << " extraInfo:" << extraInfo;
|
||||
|
||||
if (!_domainConnectionRefusals.contains(reasonMessage)) {
|
||||
_domainConnectionRefusals.insert(reasonMessage);
|
||||
emit domainConnectionRefused(reasonMessage, (int)reasonCode);
|
||||
emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
|
||||
}
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
|
|
@ -123,7 +123,7 @@ signals:
|
|||
void settingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void settingsReceiveFail();
|
||||
|
||||
void domainConnectionRefused(QString reasonMessage, int reason);
|
||||
void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo);
|
||||
|
||||
private:
|
||||
bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode);
|
||||
|
|
|
@ -745,8 +745,32 @@ void LimitedNodeList::removeSilentNodes() {
|
|||
const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442;
|
||||
const int NUM_BYTES_STUN_HEADER = 20;
|
||||
|
||||
void LimitedNodeList::sendSTUNRequest() {
|
||||
|
||||
void LimitedNodeList::makeSTUNRequestPacket(char* stunRequestPacket) {
|
||||
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);
|
||||
}
|
||||
|
||||
void LimitedNodeList::sendSTUNRequest() {
|
||||
if (!_stunSockAddr.getAddress().isNull()) {
|
||||
const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10;
|
||||
|
||||
|
@ -762,36 +786,14 @@ void LimitedNodeList::sendSTUNRequest() {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
makeSTUNRequestPacket(stunRequestPacket);
|
||||
flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest);
|
||||
|
||||
_nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
|
||||
}
|
||||
}
|
||||
|
||||
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
|
||||
bool LimitedNodeList::parseSTUNResponse(udt::BasePacket* packet,
|
||||
QHostAddress& newPublicAddress, uint16_t& newPublicPort) {
|
||||
// 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;
|
||||
|
@ -803,71 +805,79 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packe
|
|||
|
||||
if (memcmp(packet->getData() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH,
|
||||
&RFC_5389_MAGIC_COOKIE_NETWORK_ORDER,
|
||||
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) {
|
||||
sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
|
||||
while (attributeStartIndex < packet->getDataSize()) {
|
||||
// enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE
|
||||
while (attributeStartIndex < packet->getDataSize()) {
|
||||
if (memcmp(packet->getData() + 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;
|
||||
|
||||
if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) {
|
||||
const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4;
|
||||
const int NUM_BYTES_FAMILY_ALIGN = 1;
|
||||
const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8;
|
||||
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
|
||||
|
||||
int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN;
|
||||
uint8_t addressFamily = 0;
|
||||
memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily));
|
||||
|
||||
uint8_t addressFamily = 0;
|
||||
memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily));
|
||||
byteIndex += sizeof(addressFamily);
|
||||
|
||||
byteIndex += sizeof(addressFamily);
|
||||
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
|
||||
// grab the X-Port
|
||||
uint16_t xorMappedPort = 0;
|
||||
memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort));
|
||||
|
||||
if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) {
|
||||
// grab the X-Port
|
||||
uint16_t xorMappedPort = 0;
|
||||
memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort));
|
||||
newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
|
||||
|
||||
uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16);
|
||||
byteIndex += sizeof(xorMappedPort);
|
||||
|
||||
byteIndex += sizeof(xorMappedPort);
|
||||
// grab the X-Address
|
||||
uint32_t xorMappedAddress = 0;
|
||||
memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress));
|
||||
|
||||
// grab the X-Address
|
||||
uint32_t xorMappedAddress = 0;
|
||||
memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress));
|
||||
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
||||
|
||||
uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER);
|
||||
|
||||
QHostAddress newPublicAddress(stunAddress);
|
||||
|
||||
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
|
||||
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
|
||||
|
||||
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
|
||||
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
|
||||
_publicSockAddr.getPort());
|
||||
|
||||
if (!_hasCompletedInitialSTUN) {
|
||||
// if we're here we have definitely completed our initial STUN sequence
|
||||
stopInitialSTUNUpdate(true);
|
||||
}
|
||||
|
||||
emit publicSockAddrChanged(_publicSockAddr);
|
||||
|
||||
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
|
||||
}
|
||||
|
||||
// we're done reading the packet so we can return now
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// push forward attributeStartIndex by the length of this attribute
|
||||
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
|
||||
|
||||
uint16_t attributeLength = 0;
|
||||
memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
|
||||
sizeof(attributeLength));
|
||||
attributeLength = ntohs(attributeLength);
|
||||
|
||||
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
|
||||
// QHostAddress newPublicAddress(stunAddress);
|
||||
newPublicAddress = QHostAddress(stunAddress);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// push forward attributeStartIndex by the length of this attribute
|
||||
const int NUM_BYTES_ATTRIBUTE_TYPE = 2;
|
||||
|
||||
uint16_t attributeLength = 0;
|
||||
memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE,
|
||||
sizeof(attributeLength));
|
||||
attributeLength = ntohs(attributeLength);
|
||||
|
||||
attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
|
||||
uint16_t newPublicPort;
|
||||
QHostAddress newPublicAddress;
|
||||
if (parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
|
||||
|
||||
if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) {
|
||||
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
|
||||
|
||||
qCDebug(networking, "New public socket received from STUN server is %s:%hu",
|
||||
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
|
||||
_publicSockAddr.getPort());
|
||||
|
||||
if (!_hasCompletedInitialSTUN) {
|
||||
// if we're here we have definitely completed our initial STUN sequence
|
||||
stopInitialSTUNUpdate(true);
|
||||
}
|
||||
|
||||
emit publicSockAddrChanged(_publicSockAddr);
|
||||
|
||||
flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ public:
|
|||
const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS,
|
||||
const QUuid& connectionSecret = QUuid());
|
||||
|
||||
static bool parseSTUNResponse(udt::BasePacket* packet, QHostAddress& newPublicAddress, uint16_t& newPublicPort);
|
||||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||
|
||||
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||
|
@ -166,8 +167,8 @@ public:
|
|||
std::unique_ptr<NLPacket> constructPingPacket(PingType_t pingType = PingType::Agnostic);
|
||||
std::unique_ptr<NLPacket> constructPingReplyPacket(ReceivedMessage& message);
|
||||
|
||||
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
||||
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
|
||||
static std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
||||
static std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
|
||||
|
||||
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
|
||||
|
||||
|
@ -232,6 +233,9 @@ public:
|
|||
bool packetVersionMatch(const udt::Packet& packet);
|
||||
bool isPacketVerified(const udt::Packet& packet);
|
||||
|
||||
static void makeSTUNRequestPacket(char* stunRequestPacket);
|
||||
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void eraseAllNodes();
|
||||
|
@ -275,7 +279,7 @@ protected:
|
|||
LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
||||
LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
||||
void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
|
||||
|
||||
|
||||
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode,
|
||||
const HifiSockAddr& overridenSockAddr);
|
||||
qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr,
|
||||
|
@ -284,7 +288,7 @@ protected:
|
|||
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
|
||||
|
||||
void setLocalSocket(const HifiSockAddr& sockAddr);
|
||||
|
||||
|
||||
bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet);
|
||||
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 18; // Introduction of node ignore request (which replaced an unused packet tpye)
|
||||
|
||||
case PacketType::DomainConnectionDenied:
|
||||
return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesReasonCode);
|
||||
return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesExtraInfo);
|
||||
|
||||
case PacketType::DomainConnectRequest:
|
||||
return static_cast<PacketVersion>(DomainConnectRequestVersion::HasProtocolVersions);
|
||||
|
|
|
@ -206,7 +206,8 @@ enum class DomainConnectRequestVersion : PacketVersion {
|
|||
|
||||
enum class DomainConnectionDeniedVersion : PacketVersion {
|
||||
ReasonMessageOnly = 17,
|
||||
IncludesReasonCode
|
||||
IncludesReasonCode,
|
||||
IncludesExtraInfo
|
||||
};
|
||||
|
||||
enum class DomainServerAddedNodeVersion : PacketVersion {
|
||||
|
|
|
@ -184,7 +184,7 @@ var toolBar = (function () {
|
|||
properties.position = position;
|
||||
entityID = Entities.addEntity(properties);
|
||||
} else {
|
||||
Window.alert("Can't create " + properties.type + ": " + properties.type + " would be out of bounds.");
|
||||
Window.notifyEditError("Can't create " + properties.type + ": " + properties.type + " would be out of bounds.");
|
||||
}
|
||||
|
||||
selectionManager.clearSelections();
|
||||
|
@ -445,7 +445,7 @@ var toolBar = (function () {
|
|||
return;
|
||||
}
|
||||
if (active && !Entities.canRez() && !Entities.canRezTmp()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
return;
|
||||
}
|
||||
Messages.sendLocalMessage("edit-events", JSON.stringify({
|
||||
|
@ -1082,13 +1082,13 @@ function handeMenuEvent(menuItem) {
|
|||
deleteSelectedEntities();
|
||||
} else if (menuItem === "Export Entities") {
|
||||
if (!selectionManager.hasSelection()) {
|
||||
Window.alert("No entities have been selected.");
|
||||
Window.notifyEditError("No entities have been selected.");
|
||||
} else {
|
||||
var filename = Window.save("Select Where to Save", "", "*.json");
|
||||
if (filename) {
|
||||
var success = Clipboard.exportEntities(filename, selectionManager.selections);
|
||||
if (!success) {
|
||||
Window.alert("Export failed.");
|
||||
Window.notifyEditError("Export failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1156,7 +1156,7 @@ function getPositionToImportEntity() {
|
|||
}
|
||||
function importSVO(importURL) {
|
||||
if (!Entities.canAdjustLocks()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
|
||||
Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1188,10 +1188,10 @@ function importSVO(importURL) {
|
|||
|
||||
Window.raiseMainWindow();
|
||||
} else {
|
||||
Window.alert("Can't import objects: objects would be out of bounds.");
|
||||
Window.notifyEditError("Can't import objects: objects would be out of bounds.");
|
||||
}
|
||||
} else {
|
||||
Window.alert("There was an error importing the entity file.");
|
||||
Window.notifyEditError("There was an error importing the entity file.");
|
||||
}
|
||||
|
||||
Overlays.editOverlay(importingSVOTextOverlay, {
|
||||
|
@ -1481,7 +1481,7 @@ var PropertiesTool = function (opts) {
|
|||
// If any of the natural dimensions are not 0, resize
|
||||
if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 &&
|
||||
naturalDimensions.z === 0) {
|
||||
Window.alert("Cannot reset entity to its natural dimensions: Model URL" +
|
||||
Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" +
|
||||
" is invalid or the model has not yet been loaded.");
|
||||
} else {
|
||||
Entities.editEntity(selectionManager.selections[i], {
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
// }
|
||||
// }
|
||||
|
||||
/* global Script, Controller, Overlays, SoundArray, Quat, Vec3, MyAvatar, Menu, HMD, AudioDevice, LODManager, Settings, Camera */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
Script.include("./libraries/soundArray.js");
|
||||
|
@ -76,11 +78,9 @@ var fontSize = 12.0;
|
|||
var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades
|
||||
var PERSIST_TIME_3D = 15.0;
|
||||
var persistTime = PERSIST_TIME_2D;
|
||||
var clickedText = false;
|
||||
var frame = 0;
|
||||
var ourWidth = Window.innerWidth;
|
||||
var ourHeight = Window.innerHeight;
|
||||
var text = "placeholder";
|
||||
var ctrlIsPressed = false;
|
||||
var ready = true;
|
||||
var MENU_NAME = 'Tools > Notifications';
|
||||
|
@ -97,12 +97,14 @@ var NotificationType = {
|
|||
WINDOW_RESIZE: 3,
|
||||
LOD_WARNING: 4,
|
||||
CONNECTION_REFUSED: 5,
|
||||
EDIT_ERROR: 6,
|
||||
properties: [
|
||||
{ text: "Mute Toggle" },
|
||||
{ text: "Snapshot" },
|
||||
{ text: "Window Resize" },
|
||||
{ text: "Level of Detail" },
|
||||
{ text: "Connection Refused" }
|
||||
{ text: "Connection Refused" },
|
||||
{ text: "Edit error" }
|
||||
],
|
||||
getTypeFromMenuItem: function(menuItemName) {
|
||||
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
|
||||
|
@ -253,6 +255,9 @@ function notify(notice, button, height, imageProperties, image) {
|
|||
|
||||
positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y);
|
||||
|
||||
notice.parentID = MyAvatar.sessionUUID;
|
||||
notice.parentJointIndex = -2;
|
||||
|
||||
if (!image) {
|
||||
notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE;
|
||||
notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE;
|
||||
|
@ -270,6 +275,8 @@ function notify(notice, button, height, imageProperties, image) {
|
|||
button.url = button.imageURL;
|
||||
button.scale = button.width * NOTIFICATION_3D_SCALE;
|
||||
button.isFacingAvatar = false;
|
||||
button.parentID = MyAvatar.sessionUUID;
|
||||
button.parentJointIndex = -2;
|
||||
|
||||
buttons.push((Overlays.addOverlay("image3d", button)));
|
||||
overlay3DDetails.push({
|
||||
|
@ -279,6 +286,34 @@ function notify(notice, button, height, imageProperties, image) {
|
|||
width: noticeWidth,
|
||||
height: noticeHeight
|
||||
});
|
||||
|
||||
|
||||
var defaultEyePosition,
|
||||
avatarOrientation,
|
||||
notificationPosition,
|
||||
notificationOrientation,
|
||||
buttonPosition;
|
||||
|
||||
if (isOnHMD && notifications.length > 0) {
|
||||
// Update 3D overlays to maintain positions relative to avatar
|
||||
defaultEyePosition = MyAvatar.getDefaultEyePosition();
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
|
||||
for (i = 0; i < notifications.length; i += 1) {
|
||||
notificationPosition = Vec3.sum(defaultEyePosition,
|
||||
Vec3.multiplyQbyV(avatarOrientation,
|
||||
overlay3DDetails[i].notificationPosition));
|
||||
notificationOrientation = Quat.multiply(avatarOrientation,
|
||||
overlay3DDetails[i].notificationOrientation);
|
||||
buttonPosition = Vec3.sum(defaultEyePosition,
|
||||
Vec3.multiplyQbyV(avatarOrientation,
|
||||
overlay3DDetails[i].buttonPosition));
|
||||
Overlays.editOverlay(notifications[i], { position: notificationPosition,
|
||||
rotation: notificationOrientation });
|
||||
Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation });
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!image) {
|
||||
notificationText = Overlays.addOverlay("text", notice);
|
||||
|
@ -429,11 +464,6 @@ function update() {
|
|||
noticeOut,
|
||||
buttonOut,
|
||||
arraysOut,
|
||||
defaultEyePosition,
|
||||
avatarOrientation,
|
||||
notificationPosition,
|
||||
notificationOrientation,
|
||||
buttonPosition,
|
||||
positions,
|
||||
i,
|
||||
j,
|
||||
|
@ -457,7 +487,8 @@ function update() {
|
|||
Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY });
|
||||
Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 });
|
||||
if (isOnHMD) {
|
||||
positions = calculate3DOverlayPositions(overlay3DDetails[i].width, overlay3DDetails[i].height, locationY);
|
||||
positions = calculate3DOverlayPositions(overlay3DDetails[i].width,
|
||||
overlay3DDetails[i].height, locationY);
|
||||
overlay3DDetails[i].notificationOrientation = positions.notificationOrientation;
|
||||
overlay3DDetails[i].notificationPosition = positions.notificationPosition;
|
||||
overlay3DDetails[i].buttonPosition = positions.buttonPosition;
|
||||
|
@ -480,22 +511,6 @@ function update() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnHMD && notifications.length > 0) {
|
||||
// Update 3D overlays to maintain positions relative to avatar
|
||||
defaultEyePosition = MyAvatar.getDefaultEyePosition();
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
|
||||
for (i = 0; i < notifications.length; i += 1) {
|
||||
notificationPosition = Vec3.sum(defaultEyePosition,
|
||||
Vec3.multiplyQbyV(avatarOrientation, overlay3DDetails[i].notificationPosition));
|
||||
notificationOrientation = Quat.multiply(avatarOrientation, overlay3DDetails[i].notificationOrientation);
|
||||
buttonPosition = Vec3.sum(defaultEyePosition,
|
||||
Vec3.multiplyQbyV(avatarOrientation, overlay3DDetails[i].buttonPosition));
|
||||
Overlays.editOverlay(notifications[i], { position: notificationPosition, rotation: notificationOrientation });
|
||||
Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var STARTUP_TIMEOUT = 500, // ms
|
||||
|
@ -532,12 +547,17 @@ function onDomainConnectionRefused(reason) {
|
|||
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED);
|
||||
}
|
||||
|
||||
function onEditError(msg) {
|
||||
createNotification(wordWrap(msg), NotificationType.EDIT_ERROR);
|
||||
}
|
||||
|
||||
|
||||
function onSnapshotTaken(path, notify) {
|
||||
if (notify) {
|
||||
var imageProperties = {
|
||||
path: "file:///" + path,
|
||||
aspectRatio: Window.innerWidth / Window.innerHeight
|
||||
}
|
||||
};
|
||||
createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties);
|
||||
}
|
||||
}
|
||||
|
@ -571,8 +591,6 @@ function keyReleaseEvent(key) {
|
|||
|
||||
// Triggers notification on specific key driven events
|
||||
function keyPressEvent(key) {
|
||||
var noteString;
|
||||
|
||||
if (key.key === 16777249) {
|
||||
ctrlIsPressed = true;
|
||||
}
|
||||
|
@ -622,13 +640,13 @@ function menuItemEvent(menuItem) {
|
|||
}
|
||||
|
||||
LODManager.LODDecreased.connect(function() {
|
||||
var warningText = "\n"
|
||||
+ "Due to the complexity of the content, the \n"
|
||||
+ "level of detail has been decreased. "
|
||||
+ "You can now see: \n"
|
||||
+ LODManager.getLODFeedbackText();
|
||||
var warningText = "\n" +
|
||||
"Due to the complexity of the content, the \n" +
|
||||
"level of detail has been decreased. " +
|
||||
"You can now see: \n" +
|
||||
LODManager.getLODFeedbackText();
|
||||
|
||||
if (lodTextID == false) {
|
||||
if (lodTextID === false) {
|
||||
lodTextID = createNotification(warningText, NotificationType.LOD_WARNING);
|
||||
} else {
|
||||
Overlays.editOverlay(lodTextID, { text: warningText });
|
||||
|
@ -644,6 +662,7 @@ Script.scriptEnding.connect(scriptEnding);
|
|||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
|
||||
Window.snapshotTaken.connect(onSnapshotTaken);
|
||||
Window.notifyEditError = onEditError;
|
||||
|
||||
setup();
|
||||
|
||||
|
|
|
@ -7,3 +7,6 @@ set_target_properties(udt-test PROPERTIES FOLDER "Tools")
|
|||
|
||||
add_subdirectory(vhacd-util)
|
||||
set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")
|
||||
|
||||
add_subdirectory(ice-client)
|
||||
set_target_properties(ice-client PROPERTIES FOLDER "Tools")
|
||||
|
|
3
tools/ice-client/CMakeLists.txt
Normal file
3
tools/ice-client/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
set(TARGET_NAME ice-client)
|
||||
setup_hifi_project(Core Widgets)
|
||||
link_hifi_libraries(shared networking)
|
387
tools/ice-client/src/ICEClientApp.cpp
Normal file
387
tools/ice-client/src/ICEClientApp.cpp
Normal file
|
@ -0,0 +1,387 @@
|
|||
//
|
||||
// ICEClientApp.cpp
|
||||
// tools/ice-client/src
|
||||
//
|
||||
// Created by Seth Alves on 3/5/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QLoggingCategory>
|
||||
#include <QCommandLineParser>
|
||||
#include <PathUtils.h>
|
||||
#include <LimitedNodeList.h>
|
||||
#include <NetworkLogging.h>
|
||||
|
||||
#include "ICEClientApp.h"
|
||||
|
||||
ICEClientApp::ICEClientApp(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv)
|
||||
{
|
||||
// parse command-line
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("High Fidelity ICE client");
|
||||
parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption helpOption = parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption verboseOutput("v", "verbose output");
|
||||
parser.addOption(verboseOutput);
|
||||
|
||||
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
|
||||
parser.addOption(iceServerAddressOption);
|
||||
|
||||
const QCommandLineOption howManyTimesOption("n", "how many times to cycle", "1");
|
||||
parser.addOption(howManyTimesOption);
|
||||
|
||||
const QCommandLineOption domainIDOption("d", "domain-server uuid", "00000000-0000-0000-0000-000000000000");
|
||||
parser.addOption(domainIDOption);
|
||||
|
||||
const QCommandLineOption cacheSTUNOption("s", "cache stun-server response");
|
||||
parser.addOption(cacheSTUNOption);
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qCritical() << parser.errorText() << endl;
|
||||
parser.showHelp();
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
if (parser.isSet(helpOption)) {
|
||||
parser.showHelp();
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
_verbose = parser.isSet(verboseOutput);
|
||||
if (!_verbose) {
|
||||
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
|
||||
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
|
||||
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
|
||||
}
|
||||
|
||||
_stunSockAddr = HifiSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT, true);
|
||||
|
||||
_cacheSTUNResult = parser.isSet(cacheSTUNOption);
|
||||
|
||||
if (parser.isSet(howManyTimesOption)) {
|
||||
_actionMax = parser.value(howManyTimesOption).toInt();
|
||||
} else {
|
||||
_actionMax = 1;
|
||||
}
|
||||
|
||||
if (parser.isSet(domainIDOption)) {
|
||||
_domainID = QUuid(parser.value(domainIDOption));
|
||||
if (_verbose) {
|
||||
qDebug() << "domain-server ID is" << _domainID;
|
||||
}
|
||||
}
|
||||
|
||||
_iceServerAddr = HifiSockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT);
|
||||
if (parser.isSet(iceServerAddressOption)) {
|
||||
// parse the IP and port combination for this target
|
||||
QString hostnamePortString = parser.value(iceServerAddressOption);
|
||||
|
||||
QHostAddress address { hostnamePortString.left(hostnamePortString.indexOf(':')) };
|
||||
quint16 port { (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt() };
|
||||
if (port == 0) {
|
||||
port = ICE_SERVER_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (address.isNull()) {
|
||||
qCritical() << "Could not parse an IP address and port combination from" << hostnamePortString << "-" <<
|
||||
"The parsed IP was" << address.toString() << "and the parsed port was" << port;
|
||||
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
} else {
|
||||
_iceServerAddr = HifiSockAddr(address, port);
|
||||
}
|
||||
}
|
||||
|
||||
if (_verbose) {
|
||||
qDebug() << "ICE-server address is" << _iceServerAddr;
|
||||
}
|
||||
|
||||
setState(lookUpStunServer);
|
||||
|
||||
QTimer* doTimer = new QTimer(this);
|
||||
connect(doTimer, &QTimer::timeout, this, &ICEClientApp::doSomething);
|
||||
doTimer->start(200);
|
||||
}
|
||||
|
||||
ICEClientApp::~ICEClientApp() {
|
||||
delete _socket;
|
||||
}
|
||||
|
||||
void ICEClientApp::setState(int newState) {
|
||||
_state = newState;
|
||||
}
|
||||
|
||||
void ICEClientApp::closeSocket() {
|
||||
_domainServerPeerSet = false;
|
||||
delete _socket;
|
||||
_socket = nullptr;
|
||||
}
|
||||
|
||||
void ICEClientApp::openSocket() {
|
||||
if (_socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
_socket = new udt::Socket();
|
||||
unsigned int localPort = 0;
|
||||
_socket->bind(QHostAddress::AnyIPv4, localPort);
|
||||
_socket->setPacketHandler([this](std::unique_ptr<udt::Packet> packet) { processPacket(std::move(packet)); });
|
||||
_socket->addUnfilteredHandler(_stunSockAddr,
|
||||
[this](std::unique_ptr<udt::BasePacket> packet) {
|
||||
processSTUNResponse(std::move(packet));
|
||||
});
|
||||
|
||||
if (_verbose) {
|
||||
qDebug() << "local port is" << _socket->localPort();
|
||||
}
|
||||
_localSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort());
|
||||
_publicSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort());
|
||||
_domainPingCount = 0;
|
||||
}
|
||||
|
||||
void ICEClientApp::doSomething() {
|
||||
if (_actionMax > 0 && _actionCount >= _actionMax) {
|
||||
// time to stop.
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
|
||||
} else if (_state == lookUpStunServer) {
|
||||
// lookup STUN server address
|
||||
if (!_stunSockAddr.getAddress().isNull()) {
|
||||
if (_verbose) {
|
||||
qDebug() << "stun server is" << _stunSockAddr;
|
||||
}
|
||||
setState(sendStunRequestPacket);
|
||||
} else {
|
||||
if (_verbose) {
|
||||
qDebug() << "_stunSockAddr is" << _stunSockAddr.getAddress();
|
||||
}
|
||||
QCoreApplication::exit(stunFailureExitStatus);
|
||||
}
|
||||
|
||||
} else if (_state == sendStunRequestPacket) {
|
||||
// send STUN request packet
|
||||
closeSocket();
|
||||
openSocket();
|
||||
|
||||
if (!_cacheSTUNResult || !_stunResultSet) {
|
||||
const int NUM_BYTES_STUN_HEADER = 20;
|
||||
char stunRequestPacket[NUM_BYTES_STUN_HEADER];
|
||||
LimitedNodeList::makeSTUNRequestPacket(stunRequestPacket);
|
||||
if (_verbose) {
|
||||
qDebug() << "sending STUN request";
|
||||
}
|
||||
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
|
||||
_stunResponseTimerCanceled = false;
|
||||
_stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] {
|
||||
if (_stunResponseTimerCanceled) {
|
||||
return;
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << "timeout waiting for stun-server response";
|
||||
}
|
||||
QCoreApplication::exit(stunFailureExitStatus);
|
||||
});
|
||||
|
||||
setState(waitForStunResponse);
|
||||
} else {
|
||||
if (_verbose) {
|
||||
qDebug() << "using cached STUN response";
|
||||
}
|
||||
_publicSockAddr.setPort(_socket->localPort());
|
||||
setState(talkToIceServer);
|
||||
}
|
||||
|
||||
} else if (_state == talkToIceServer) {
|
||||
QUuid peerID;
|
||||
if (_domainID == QUuid()) {
|
||||
// pick a random domain-id which will fail
|
||||
peerID = QUuid::createUuid();
|
||||
setState(pause0);
|
||||
} else {
|
||||
// use the domain UUID given on the command-line
|
||||
peerID = _domainID;
|
||||
setState(waitForIceReply);
|
||||
}
|
||||
_sessionUUID = QUuid::createUuid();
|
||||
if (_verbose) {
|
||||
qDebug() << "I am" << _sessionUUID;
|
||||
}
|
||||
|
||||
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
|
||||
_iceResponseTimerCanceled = false;
|
||||
_iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] {
|
||||
if (_iceResponseTimerCanceled) {
|
||||
return;
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << "timeout waiting for ice-server response";
|
||||
}
|
||||
QCoreApplication::exit(iceFailureExitStatus);
|
||||
});
|
||||
} else if (_state == pause0) {
|
||||
setState(pause1);
|
||||
} else if (_state == pause1) {
|
||||
if (_verbose) {
|
||||
qDebug() << "";
|
||||
}
|
||||
closeSocket();
|
||||
setState(sendStunRequestPacket);
|
||||
_actionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
|
||||
const QUuid& clientID, const QUuid& peerID) {
|
||||
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
|
||||
|
||||
QDataStream iceDataStream(icePacket.get());
|
||||
iceDataStream << clientID << _publicSockAddr << _localSockAddr;
|
||||
|
||||
if (packetType == PacketType::ICEServerQuery) {
|
||||
assert(!peerID.isNull());
|
||||
|
||||
iceDataStream << peerID;
|
||||
|
||||
if (_verbose) {
|
||||
qDebug() << "Sending packet to ICE server to request connection info for peer with ID"
|
||||
<< uuidStringWithoutCurlyBraces(peerID);
|
||||
}
|
||||
}
|
||||
|
||||
// fillPacketHeader(packet, connectionSecret);
|
||||
_socket->writePacket(*icePacket, _iceServerAddr);
|
||||
}
|
||||
|
||||
void ICEClientApp::checkDomainPingCount() {
|
||||
_domainPingCount++;
|
||||
if (_domainPingCount > 5) {
|
||||
if (_verbose) {
|
||||
qDebug() << "too many unanswered pings to domain-server.";
|
||||
}
|
||||
QCoreApplication::exit(domainPingExitStatus);
|
||||
}
|
||||
}
|
||||
|
||||
void ICEClientApp::icePingDomainServer() {
|
||||
if (!_domainServerPeerSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_verbose) {
|
||||
qDebug() << "ice-pinging domain-server: " << _domainServerPeer;
|
||||
}
|
||||
|
||||
auto localPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Local, _sessionUUID);
|
||||
_socket->writePacket(*localPingPacket, _domainServerPeer.getLocalSocket());
|
||||
|
||||
auto publicPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Public, _sessionUUID);
|
||||
_socket->writePacket(*publicPingPacket, _domainServerPeer.getPublicSocket());
|
||||
checkDomainPingCount();
|
||||
}
|
||||
|
||||
void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet) {
|
||||
if (_verbose) {
|
||||
qDebug() << "got stun response";
|
||||
}
|
||||
if (_state != waitForStunResponse) {
|
||||
if (_verbose) {
|
||||
qDebug() << "got unexpected stun response";
|
||||
}
|
||||
QCoreApplication::exit(stunFailureExitStatus);
|
||||
}
|
||||
|
||||
_stunResponseTimer.stop();
|
||||
_stunResponseTimerCanceled = true;
|
||||
|
||||
uint16_t newPublicPort;
|
||||
QHostAddress newPublicAddress;
|
||||
if (LimitedNodeList::parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
|
||||
_publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort);
|
||||
if (_verbose) {
|
||||
qDebug() << "My public address is" << _publicSockAddr;
|
||||
}
|
||||
_stunResultSet = true;
|
||||
setState(talkToIceServer);
|
||||
} else {
|
||||
QCoreApplication::exit(stunFailureExitStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ICEClientApp::processPacket(std::unique_ptr<udt::Packet> packet) {
|
||||
std::unique_ptr<NLPacket> nlPacket = NLPacket::fromBase(std::move(packet));
|
||||
|
||||
if (nlPacket->getPayloadSize() < NLPacket::localHeaderSize(PacketType::ICEServerHeartbeat)) {
|
||||
if (_verbose) {
|
||||
qDebug() << "got a short packet.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QSharedPointer<ReceivedMessage> message = QSharedPointer<ReceivedMessage>::create(*nlPacket);
|
||||
const HifiSockAddr& senderAddr = message->getSenderSockAddr();
|
||||
|
||||
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
|
||||
// cancel the timeout timer
|
||||
_iceResponseTimer.stop();
|
||||
_iceResponseTimerCanceled = true;
|
||||
|
||||
QDataStream iceResponseStream(message->getMessage());
|
||||
if (!_domainServerPeerSet) {
|
||||
iceResponseStream >> _domainServerPeer;
|
||||
if (_verbose) {
|
||||
qDebug() << "got ICEServerPeerInformation from" << _domainServerPeer;
|
||||
}
|
||||
_domainServerPeerSet = true;
|
||||
|
||||
icePingDomainServer();
|
||||
_pingDomainTimer = new QTimer(this);
|
||||
connect(_pingDomainTimer, &QTimer::timeout, this, &ICEClientApp::icePingDomainServer);
|
||||
_pingDomainTimer->start(500);
|
||||
} else {
|
||||
NetworkPeer domainServerPeer;
|
||||
iceResponseStream >> domainServerPeer;
|
||||
if (_verbose) {
|
||||
qDebug() << "got repeat ICEServerPeerInformation from" << domainServerPeer;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (nlPacket->getType() == PacketType::ICEPing) {
|
||||
if (_verbose) {
|
||||
qDebug() << "got packet: " << nlPacket->getType();
|
||||
}
|
||||
auto replyPacket = LimitedNodeList::constructICEPingReplyPacket(*message, _sessionUUID);
|
||||
_socket->writePacket(*replyPacket, senderAddr);
|
||||
checkDomainPingCount();
|
||||
|
||||
} else if (nlPacket->getType() == PacketType::ICEPingReply) {
|
||||
if (_verbose) {
|
||||
qDebug() << "got packet: " << nlPacket->getType();
|
||||
}
|
||||
if (_domainServerPeerSet && _state == waitForIceReply &&
|
||||
(senderAddr == _domainServerPeer.getLocalSocket() ||
|
||||
senderAddr == _domainServerPeer.getPublicSocket())) {
|
||||
|
||||
delete _pingDomainTimer;
|
||||
_pingDomainTimer = nullptr;
|
||||
|
||||
setState(pause0);
|
||||
} else {
|
||||
if (_verbose) {
|
||||
qDebug() << "got unexpected ICEPingReply" << senderAddr;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (_verbose) {
|
||||
qDebug() << "got unexpected packet: " << nlPacket->getType();
|
||||
}
|
||||
}
|
||||
}
|
96
tools/ice-client/src/ICEClientApp.h
Normal file
96
tools/ice-client/src/ICEClientApp.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// ICEClientApp.h
|
||||
// tools/ice-client/src
|
||||
//
|
||||
// Created by Seth Alves on 2016-9-16
|
||||
// Copyright 2016 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_ICEClientApp_h
|
||||
#define hifi_ICEClientApp_h
|
||||
|
||||
#include <QApplication>
|
||||
#include <udt/Constants.h>
|
||||
#include <udt/Socket.h>
|
||||
#include <ReceivedMessage.h>
|
||||
#include <NetworkPeer.h>
|
||||
|
||||
|
||||
class ICEClientApp : public QCoreApplication {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ICEClientApp(int argc, char* argv[]);
|
||||
~ICEClientApp();
|
||||
|
||||
const int stunFailureExitStatus { 1 };
|
||||
const int iceFailureExitStatus { 2 };
|
||||
const int domainPingExitStatus { 3 };
|
||||
|
||||
const int stunResponseTimeoutMilliSeconds { 2000 };
|
||||
const int iceResponseTimeoutMilliSeconds { 2000 };
|
||||
|
||||
private:
|
||||
enum State {
|
||||
lookUpStunServer, // 0
|
||||
sendStunRequestPacket, // 1
|
||||
waitForStunResponse, // 2
|
||||
talkToIceServer, // 3
|
||||
waitForIceReply, // 4
|
||||
pause0, // 5
|
||||
pause1 // 6
|
||||
};
|
||||
|
||||
void closeSocket();
|
||||
void openSocket();
|
||||
|
||||
void setState(int newState);
|
||||
|
||||
void doSomething();
|
||||
void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
|
||||
const QUuid& clientID, const QUuid& peerID);
|
||||
void icePingDomainServer();
|
||||
void processSTUNResponse(std::unique_ptr<udt::BasePacket> packet);
|
||||
void processPacket(std::unique_ptr<udt::Packet> packet);
|
||||
void checkDomainPingCount();
|
||||
|
||||
bool _verbose;
|
||||
bool _cacheSTUNResult; // should we only talk to stun server once?
|
||||
bool _stunResultSet { false }; // have we already talked to stun server?
|
||||
|
||||
HifiSockAddr _stunSockAddr;
|
||||
|
||||
unsigned int _actionCount { 0 };
|
||||
unsigned int _actionMax { 0 };
|
||||
|
||||
QUuid _sessionUUID;
|
||||
QUuid _domainID;
|
||||
|
||||
QTimer* _pingDomainTimer { nullptr };
|
||||
|
||||
HifiSockAddr _iceServerAddr;
|
||||
|
||||
HifiSockAddr _localSockAddr;
|
||||
HifiSockAddr _publicSockAddr;
|
||||
udt::Socket* _socket { nullptr };
|
||||
|
||||
bool _domainServerPeerSet { false };
|
||||
NetworkPeer _domainServerPeer;
|
||||
|
||||
int _state { 0 };
|
||||
|
||||
QTimer _stunResponseTimer;
|
||||
bool _stunResponseTimerCanceled { false };
|
||||
QTimer _iceResponseTimer;
|
||||
bool _iceResponseTimerCanceled { false };
|
||||
int _domainPingCount { 0 };
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif //hifi_ICEClientApp_h
|
23
tools/ice-client/src/main.cpp
Normal file
23
tools/ice-client/src/main.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tools/ice-client/src
|
||||
//
|
||||
// Created by Seth Alves on 2016-9-16
|
||||
// Copyright 2016 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ICEClientApp.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
ICEClientApp app(argc, argv);
|
||||
return app.exec();
|
||||
}
|
Loading…
Reference in a new issue